• 目录
企业集成模式:设计、构建和部署消息传递解决方案
作者: 格雷戈尔·霍普鲍比·伍尔夫
 
出版商:艾迪生韦斯利
发布日期: 2003 年 10 月 10 日
国际标准书号:0-321-20068-3
页数: 736

 

 

 

 

  • 您想使用一致的视觉符号来绘制集成解决方案吗?查看前盖内部。

  • Would you like to use a con­sist­ent visual nota­tion for draw­ing in­teg­ra­tion solu­tions? Look inside the front cover.

  • 您想利用异步系统的强大功能而不陷入陷阱吗?请参阅简介中的“异步思考”。

  • Do you want to har­ness the power of asyn­chron­ous sys­tems without get­ting caught in the pit­falls? See "Think­ing Asyn­chron­ously" in the In­tro­duc­tion.

  • 您想知道哪种类型的应用程序集成最适合您的目的吗?请参阅第 2 章,集成样式。

  • Do you want to know which style of ap­plic­a­tion in­teg­ra­tion is best for your pur­poses? See Chapter 2, In­teg­ra­tion Styles.

  • 您想学习并发处理消息的技术吗?请参阅第 10 章,竞争消费者和消息调度程序。

  • Do you want to learn tech­niques for pro­cess­ing mes­sages con­cur­rently? See Chapter 10, Com­pet­ing Con­sumers and Mes­sage Dis­patcher.

  • 您想了解如何跟踪跨分布式系统流动的异步消息吗?请参阅第 11 章,消息历史记录和消息存储。

  • Do you want to learn how you can track asyn­chron­ous mes­sages as they flow across dis­trib­uted sys­tems? See Chapter 11, Mes­sage His­tory and Mes­sage Store.

  • 您是否想了解如何使用 Java Web 服务、.NET 消息队列和基于 TIBCO 的发布-订阅架构来实现使用集成模式设计的系统?请参阅第 9 章,插曲:组合消息。

  • Do you want to un­der­stand how a system de­signed using in­teg­ra­tion pat­terns can be im­ple­men­ted using Java Web ser­vices, .NET mes­sage queuing, and a TIBCO-based pub­lish-sub­scribe ar­chi­tec­ture? See Chapter 9, In­ter­lude: Com­posed Mes­saging.

经验丰富的专家 Gregor Hohpe 和 Bobby Woolf 利用多年的实践经验,展示了异步消息传递如何被证明是企业集成成功的最佳策略。然而,构建和部署消息传递解决方案给开发人员带来了许多问题。企业集成模式提供了包含65 种模式的宝贵目录,以及实际的解决方案,这些解决方案展示了消息传递的强大功能,并帮助您为企业设计有效的消息传递解决方案。

Util­iz­ing years of prac­tical ex­per­i­ence, seasoned ex­perts Gregor Hohpe and Bobby Woolf show how asyn­chron­ous mes­saging has proven to be the best strategy for en­ter­prise in­teg­ra­tion suc­cess. How­ever, build­ing and de­ploy­ing mes­saging solu­tions presents a number of prob­lems for de­ve­lopers. En­ter­prise In­teg­ra­tion Pat­terns provides an in­valu­able cata­log of sixty-five pat­terns, with real-world solu­tions that demon­strate the for­mid­able of mes­saging and help you to design ef­fect­ive mes­saging solu­tions for your en­ter­prise.

作者还提供了涵盖各种不同集成技术的示例,例如 JMS、MSMQ、TIBCO ActiveEnterprise、Microsoft BizTalk、SOAP 和 XSL。描述债券交易系统的案例研究阐释了实践中的模式,本书介绍了新兴标准,并洞察了企业集成的未来可能会发生什么。

The au­thors also in­clude ex­amples cov­er­ing a vari­ety of dif­fer­ent in­teg­ra­tion tech­no­lo­gies, such as JMS, MSMQ, TIBCO Act­iveEn­ter­prise, Mi­crosoft BizTalk, SOAP, and XSL. A case study de­scrib­ing a bond trad­ing system il­lus­trates the pat­terns in prac­tice, and the book offers a look at emer­ging stand­ards, as well as in­sights into what the future of en­ter­prise in­teg­ra­tion might hold.

本书提供了一致的词汇和视觉符号框架来描述跨多种技术的大规模集成解决方案。它还详细探讨了异步消息传递架构的优点和局限性。作者提供了有关设计将应用程序连接到消息传递系统的代码的实用建议,并提供了广泛的信息来帮助您确定何时发送消息、如何将其路由到正确的目的地以及如何监视消息传递系统的运行状况。如果您想了解如何管理、监控和维护正在使用的消息系统,请阅读本书。

This book provides a con­sist­ent vocab­u­lary and visual nota­tion frame­work to de­scribe large-scale in­teg­ra­tion solu­tions across many tech­no­lo­gies. It also ex­plores in detail the ad­vant­ages and lim­it­a­tions of asyn­chron­ous mes­saging ar­chi­tec­tures. The au­thors present prac­tical advice on design­ing code that con­nects an ap­plic­a­tion to a mes­saging system, and provide ex­tens­ive in­form­a­tion to help you de­term­ine when to send a mes­sage, how to route it to the proper des­tin­a­tion, and how to mon­itor the health of a mes­saging system. If you want to know how to manage, mon­itor, and main­tain a mes­saging system once it is in use, get this book.

  
• 目录
企业集成模式:设计、构建和部署消息传递解决方案
作者: 格雷戈尔·霍普鲍比·伍尔夫
 
出版商:艾迪生韦斯利
发布日期: 2003 年 10 月 10 日
国际标准书号:0-321-20068-3
页数: 736

 

 

 

 

   版权
   艾迪生-韦斯利签名系列
   前言
   前言
   前言
      谁应该读这本书
      你将学到什么
      本书未涵盖的内容
      本书的结构
      关于封面图片
   致谢
   介绍
      什么是消息传递?
      什么是消息系统?
      为什么使用消息传递?
      异步消息传递的挑战
      异步思考
      分布式应用程序与集成
      商业消息系统
      图案形式
      图表符号
      示例和插曲
      本书的组织结构
      入门
      支持网站
      概括
   第 1 章使用模式解决集成问题
      整合的必要性
      集成挑战
      集成模式如何提供帮助
      广阔的集成世界
      松耦合
      一分钟 EAI
      松耦合集成解决方案
      Widgets & Gadgets 'R Us:一个例子
      概括
   第 2 章集成样式
      介绍
      文件传输
      共享数据库
      远程过程调用
      消息传递
   第 3 章消息传递系统
      介绍
      留言通道
      信息
      管道和过滤器
      消息路由器
      消息翻译器
      消息端点
   第 4 章消息传递渠道
      介绍
      点对点通道
      发布订阅通道
      数据类型通道
      消息通道无效
      死信频道
      保证交付
      通道适配器
      消息桥
      消息总线
   第 5 章消息构造
      介绍
      命令信息
      文档留言
      活动讯息
      请求-回复
      退货地址
      相关标识符
      消息序列
      消息过期
      格式指示器
   第 6 章插曲:简单消息传递
      介绍
      JMS 请求-答复示例
      .NET 请求-回复示例
      JMS 发布-订阅示例
   第 7 章消息路由
      介绍
      基于内容的路由器
      消息过滤器
      动态路由器
      收件人名单
      分路器
      聚合器
      重排序器
      组合消息处理器
      分散-聚集
      路由表
      流程经理
      消息代理
   第 8 章消息转换
      介绍
      信封包装纸
      内容丰富器
      内容过滤器
      索赔支票
      标准化器
      规范数据模型
   第 9 章插曲:组合消息
      贷款经纪人示例
      使用Web服务同步实现
      MSMQ 异步实现
      使用 TIBCO ActiveEnterprise 进行异步实施
   第 10 章消息传递端点
      介绍
      消息传送网关
      消息传递映射器
      交易客户
      轮询消费者
      事件驱动的消费者
      竞争的消费者
      消息调度程序
      选择性消费者
      持久订阅者
      幂等接收器
      服务激活器
   第 11 章系统管理
      介绍
      控制总线
      車輛改道
      丝锥
      留言记录
      消息存储
      智能代理
      测试留言
      通道净化器
   第 12 章插曲:系统管理示例
      贷款经纪人系统管理
   第 13 章实践中的集成模式
      案例研究:债券定价系统
   第 14 章结束语
      企业集成的新兴标准和未来
   参考书目
   模式列表
   企业集成模式

版权

Copyright

制造商和销售商用来区分其产品的许多名称都被称为商标。如果这些名称出现在本书中,并且 Addison-Wesley 知道商标声明,则这些名称均以首字母大写或全部大写印刷。

Many of the des­ig­na­tions used by man­u­fac­tur­ers and sellers to dis­tin­guish their products are claimed as trade­marks. Where those des­ig­na­tions appear in this book, and Ad­dison-Wesley was aware of a trade­mark claim, the des­ig­na­tions have been prin­ted with ini­tial cap­ital let­ters or in all cap­it­als.

作者和出版商在准备本书的过程中非常谨慎,但没有做出任何形式的明示或暗示的保证,并且对错误或遗漏不承担任何责任。对于因使用此处包含的信息或程序而产生的偶然或间接损害,我们不承担任何责任。

The au­thors and pub­lisher have taken care in the pre­par­a­tion of this book, but make no ex­pressed or im­plied war­ranty of any kind and assume no re­spons­ib­il­ity for errors or omis­sions. No li­ab­il­ity is as­sumed for in­cid­ental or con­sequen­tial dam­ages in con­nec­tion with or arising out of the use of the in­form­a­tion or pro­grams con­tained herein.

出版商在批量购买和特价销售时提供本书折扣。获取更多资讯,请联系:

The pub­lisher offers dis­counts on this book when ordered in quant­ity for bulk pur­chases and spe­cial sales. For more in­form­a­tion, please con­tact:

     美国企业和政府销售

     (800) 382-3419

     corpsales@pearsontechgroup.com

     U.S. Cor­por­ate and Gov­ern­ment Sale

     (800) 382-3419

     corp­sales@pear­sontech­group.com

对于美国境外的销售,请联系:

For sales out­side of the U.S., please con­tact:

     国际销售

     (317) 581-3793   

     International@pearsontechgroup.com

     In­ter­na­tional Sales

     (317) 581-3793   

     in­ter­na­tional@pear­sontech­group.com

访问 Addison-Wesley 网站:www.awprofessional.com

Visit Ad­dison-Wesley on the Web: www.aw­pro­fes­sional.com

美国国会图书馆出版数据编目

Lib­rary of Con­gress Cata­loging-in-Pub­lic­a­tion Data



霍普,格雷戈尔。

   企业集成模式:设计、构建和部署消息传递解决 

 方案/Gregor Hohpe、Bobby Woolf。

        p。厘米。

   包括参考书目和索引。

   ISBN 0-321-20068-3

   1. 电信消息处理。2.管理信息 

 系统。I.伍尔夫,鲍比。二. 标题。



 TK5102.5.H5882 2003

 005.7'136dc22

                                                                     2003017989



Hohpe, Gregor.

   En­ter­prise in­teg­ra­tion pat­terns : design­ing, build­ing, and de­ploy­ing mes­saging 

 solu­tions / Gregor Hohpe, Bobby Woolf.

        p. cm.

   In­cludes bib­li­o­graph­ical ref­er­ences and index.

   ISBN 0-321-20068-3

   1. Tele­com­mu­nic­a­tion­Mes­sage pro­cess­ing. 2. Man­age­ment in­form­a­tion 

 sys­tems. I. Woolf, Bobby. II. Title.



 TK5102.5.H5882 2003

 005.7'136d­c22

                                                                     2003017989

版权所有 © 2004 培生教育公司

Copy­right © 2004 by Pear­son Edu­ca­tion, Inc.

版权所有。未经出版商事先同意,不得以任何形式或任何方式(电子、机械、复印、录制或其他方式)复制、存储或传播本出版物的任何部分。美国印刷。加拿大同步出版。

All rights re­served. No part of this pub­lic­a­tion may be re­pro­duced, stored in a re­trieval system, or trans­mit­ted, in any form, or by any means, elec­tronic, mech­an­ical, pho­to­copy­ing, re­cord­ing, or oth­er­wise, without the prior con­sent of the pub­lisher. Prin­ted in the United States of Amer­ica. Pub­lished sim­ul­tan­eously in Canada.

有关获得使用本作品材料的许可的信息,请向以下地址提交书面请求:

For in­form­a­tion on ob­tain­ing per­mis­sion for use of ma­ter­ial from this work, please submit a writ­ten re­quest to:

Pearson Education, Inc.

权利和合同部 75

Arlington Street, Suite 300

Boston, MA 02116 传真:

(617) 848-7047

Pear­son Edu­ca­tion, Inc.

Rights and Con­tracts De­part­ment

75 Ar­ling­ton Street, Suite 300

Boston, MA 02116

Fax: (617) 848-7047

文字印在再生纸上

Text prin­ted on re­cycled paper

1 2 3 4 5 6 7 8 9 10CRS0706050403

1 2 3 4 5 6 7 8 9 10CRS0706050403

首次印刷,2003 年 10 月

First print­ing, Oc­to­ber 2003

奉献精神

Dedication

致我的家人和所有在我从书本“紧缩模式”中走出来后仍然记得我的朋友

To my family and all my friends who still re­mem­ber me after I emerged from book "crunch mode"

格雷戈尔

Gregor

致我的新婚妻子莎伦

To Sharon, my new wife

鲍比

Bobby

    艾迪生-韦斯利签名系列

    The Addison-Wesley Signature Series

    艾迪生-韦斯利签名系列为读者提供了有关计算机专业人员现代技术最新趋势的实用且权威的信息。该系列基于一个简单的前提:伟大的书籍来自伟大的作者。该系列书籍均由专家顾问、世界级作者亲自挑选。这些专家很自豪能在封面上签名,他们的签名确保这些思想领袖与作者密切合作,确定主题范围、书籍范围、关键内容和整体独特性。专家签名也象征着对读者的承诺:您正在阅读一部未来的经典。

    The Ad­dison-Wesley Sig­na­ture Series provides read­ers with prac­tical and au­thor­it­at­ive in­form­a­tion on the latest trends in modern tech­no­logy for com­puter pro­fes­sion­als. The series is based on one simple premise: great books come from great au­thors. Books in the series are per­son­ally chosen by expert ad­visors, world-class au­thors in their own right. These ex­perts are proud to put their sig­na­tures on the covers, and their sig­na­tures ensure that these thought lead­ers have worked closely with au­thors to define topic cov­er­age, book scope, crit­ical con­tent, and over­all unique­ness. The expert sig­na­tures also sym­bol­ize a prom­ise to our read­ers: you are read­ing a future clas­sic.

    艾迪生-韦斯利签名系列

    The Ad­dison-Wesley Sig­na­ture Series

    签名肯特·贝克马丁·福勒

    SIGNERS: KENT BECK AND MARTIN FOWLER

    Kent Beck开创了以人为本的技术,例如 JUnit、极限编程和软件开发模式。Kent 感兴趣的是通过寻找一种同时满足经济、美学、情感和实践约束的软件开发风格来帮助团队取得好成绩。他的书重点关注软件创建者和用户的生活。

    Kent Beck has pi­on­eered people-ori­ented tech­no­lo­gies like JUnit, Ex­treme Pro­gram­ming, and pat­terns for soft­ware de­vel­op­ment. Kent is in­ter­ested in help­ing teams do well by doing good find­ing a style of soft­ware de­vel­op­ment that sim­ul­tan­eously sat­is­fies eco­nomic, aes­thetic, emo­tional, and prac­tical con­straints. His books focus on touch­ing the lives of the cre­at­ors and users of soft­ware.

    Martin Fowler是企业应用程序中对象技术的先驱。他最关心的是如何设计好软件。他专注于深入了解如何构建可持续发展的企业软件的核心。他有兴趣在技术细节背后寻找持续多年的模式、实践和原则;这些书十年后应该可以使用。马丁的标准是这些是他希望自己能写的书。

    Martin Fowler has been a pi­on­eer of object tech­no­logy in en­ter­prise ap­plic­a­tions. His cent­ral con­cern is how to design soft­ware well. He fo­cuses on get­ting to the heart of how to build en­ter­prise soft­ware that will last well into the future. He is in­ter­ested in look­ing behind the spe­cif­ics of tech­no­lo­gies to the pat­terns, prac­tices, and prin­ciples that last for many years; these books should be usable a decade from now. Martin's cri­terion is that these are books he wished he could write.

    系列中的标题

    Titles in the Series

    图形/fm01inl01.gif

    测试驱动开发:以

    Kent,ISBN:0321146530

    Test-Driven De­vel­op­ment: By Ex­ample

    Kent Beck, ISBN: 0321146530

    图形/fm01inl02.gif

    企业应用程序架构模式 Martin

    Fowler,ISBN:0321127420

    Pat­terns of En­ter­prise Ap­plic­a­tion Ar­chi­tec­ture

    Martin Fowler, ISBN: 0321127420

    超越软件架构:创建和维持获胜解决方案

    Luke Hohmann,ISBN:0201775948

    Beyond Soft­ware Ar­chi­tec­ture: Cre­at­ing and Sus­tain­ing Win­ning Solu­tions

    Luke Hohmann, ISBN: 0201775948

    企业集成模式:设计、构建和部署消息传递解决方案

    Gregor Hohpe 和 Bobby Woolf,ISBN:0321200683

    En­ter­prise In­teg­ra­tion Pat­terns: Design­ing, Build­ing, and De­ploy­ing Mes­saging Solu­tions

    Gregor Hohpe and Bobby Woolf, ISBN: 0321200683

    欲了解更多信息,请访问该系列网站:www.awprofessional.com

    For more in­form­a­tion, check out the series web site at www.aw­pro­fes­sional.com

      前言

      Foreword

      作者:约翰·克鲁皮

      by John Crupi

      当新技术出现时你会做什么?你学习技术。这正是我所做的。我学习了 J2EE(来自 Sun Microsystems,这似乎是合乎逻辑的选择)。具体来说,我通过阅读规范来关注 EJB 技术(因为还没有书籍)。然而,学习技术只是第一步,真正的目标是学习如何有效地应用技术。平台技术的好处在于它们会限制您执行某些任务。但是,就技术而言,你可以为所欲为,但如果做得不当,常常会遇到麻烦。

      What do you do when a new tech­no­logy ar­rives? You learn the tech­no­logy. This is ex­actly what I did. I stud­ied J2EE (being from Sun Mi­crosys­tems, it seemed to be the lo­gical choice). Spe­cific­ally, I fo­cused on the EJB tech­no­logy by read­ing the spe­cific­a­tions (since there were no books yet). Learn­ing the tech­no­logy, how­ever, is just the first stepthe real goal is to learn how to ef­fect­ively apply the tech­no­logy. The nice thing about plat­form tech­no­lo­gies is that they con­strain you to per­form­ing cer­tain tasks. But, as far as the tech­no­logy is con­cerned, you can do whatever you want and quite often get into trouble if you don't do things ap­pro­pri­ately.

      在过去 15 年里我看到的一件事是,软件开发人员似乎痴迷于两个领域:编程和设计,或者更具体地说,有效地编程和设计。有很多很棒的书籍告诉您用 Java 和 C# 进行某些编程的最有效方法,但告诉您如何有效设计的却少之又少。这就是本书的用武之地。当 Deepak Alur、Dan Malks 和我撰写《核心 J2EE 模式》时,我们希望帮助 J2EE 开发人员“设计”更好的代码。我们做出的最佳决定是使用模式作为选择的工件。正如 Sun 杰出工程师 James Baty 所说,“模式似乎是设计的最佳点。” 我完全同意,幸运的是,格雷戈尔和鲍比也有同样的感觉。

      One thing I've seen in the past 15 years is that there seem to be two areas that soft­ware de­ve­lopers obsess over: pro­gram­ming and designingor more spe­cific­ally, pro­gram­ming and design­ing ef­fect­ively. There are great books out there that tell you the most ef­fi­cient way to pro­gram cer­tain things in Java and C#, but far fewer tell you how to design ef­fect­ively. That's where this book comes in. When Deepak Alur, Dan Malks, and I wrote Core J2EE Pat­terns, we wanted to help J2EE de­ve­lopers "design" better code. The best de­cision we made was to use pat­terns as the ar­ti­fact of choice. As James Baty, a Sun Dis­tin­guished En­gin­eer, puts it, "Pat­terns seem to be the sweet spot of design." I couldn't agree more, and luck­ily for us, Gregor and Bobby feel the same way.

      本书重点关注一个热门且不断发展的主题:使用消息传递进行集成。消息传递不仅是集成的关键,而且很可能成为未来几年 Web 服务的主要焦点。如今,Web 服务世界中存在着如此多的噪音,仅仅确定要关注的规范和技术就是一项微妙而复杂的工作。目标保持不变,但是软件可以帮助您解决问题。正如 J2EE 和 .NET 的早期一样,Web 服务还没有太多设计帮助。许多人说 Web 服务只是解决我们现有集成问题的一种新的开放方式,我同意。但是,这并不意味着我们知道如何设计 Web 服务。这就引出了本书的精华。我相信这本书包含了我们设计 Web 服务和其他集成系统所需的许多模式。由于 Web 服务规范仍在争论中,因此 Bobby 和 Gregor 提供许多 Web 服务规范的示例是没有意义的。但是,没关系。当规范成为标准并且我们使用本书中的模式来设计通过这些标准实现的解决方案时,才会产生真正的回报。那么也许我们可以实现我们的下一个集成目标,即面向服务的架构设计。对于 Bobby 和 Gregor 来说,提供许多 Web 服务规范的示例是没有意义的。但是,没关系。当规范成为标准并且我们使用本书中的模式来设计通过这些标准实现的解决方案时,才会产生真正的回报。那么也许我们可以实现我们的下一个集成目标,即面向服务的架构设计。对于 Bobby 和 Gregor 来说,提供许多 Web 服务规范的示例是没有意义的。但是,没关系。当规范成为标准并且我们使用本书中的模式来设计通过这些标准实现的解决方案时,才会产生真正的回报。那么也许我们可以实现我们的下一个集成目标,即面向服务的架构设计。

      This book fo­cuses on a hot and grow­ing topic: in­teg­ra­tion using mes­saging. Not only is mes­saging key to in­teg­ra­tion, but it will most likely be the pre­dom­in­ant focus in Web ser­vices for years to come. There is so much noise today in the Web ser­vices world, it's a del­ic­ate and com­plex en­deavor just to identify the spe­cific­a­tions and tech­no­lo­gies to focus on. The goal re­mains the same, how­ever­soft­ware helps you solve a prob­lem. Just as in the early days of J2EE and .NET, there is not a lot of design help out there yet for Web ser­vices. Many people say Web ser­vices is just a new and open way to solve our ex­ist­ing in­teg­ra­tion prob­lem­sand I agree. But, that doesn't mean we know how to design Web ser­vices. And that brings us to the gem of this book. I be­lieve this book has many of the pat­terns we need to design Web ser­vices and other in­teg­ra­tion sys­tems. Be­cause the Web ser­vice spe­cific­a­tions are still bat­tling it out, it wouldn't have made sense for Bobby and Gregor to provide ex­amples of many of the Web ser­vice spe­cific­a­tions. But, that's okay. The real payoff will result when the spe­cific­a­tions become stand­ards and we use the pat­terns in this book to design for those solu­tions that are real­ized by these stand­ards. Then maybe we can real­ize our next in­teg­ra­tion goal of design­ing for ser­vice-ori­ented ar­chi­tec­tures.

      阅读这本书并将其放在身边。它将让您的软件职业生涯永无止境。

      Read this book and keep it by your side. It will en­hance your soft­ware career to no end.

      约翰·克鲁皮·贝塞斯达(John Crupi

      Bethesda),医学博士 2003 年

      8 月

      John Crupi

      Beth­esda, MD

      August 2003

        前言

        Foreword

        马丁·福勒

        by Martin Fowler

        当我在写《企业应用程序架构模式》一书时,我很幸运在位于罗利达勒姆的 Kyle 办公室举行的一些非正式研讨会上得到了 Kyle Brown 和 Rachel Reinitz 的深入评论。在这些会议期间,我们意识到我的工作中的一个很大的差距是异步消息系统。

        While I was work­ing on my book Pat­terns of En­ter­prise Ap­plic­a­tion Ar­chi­tec­ture, I was lucky to get some in-depth review from Kyle Brown and Rachel Rein­itz at some in­formal work­shops at Kyle's office in Raleigh-Durham. During these ses­sions, we real­ized that a big gap in my work was asyn­chron­ous mes­saging sys­tems.

        我的书中有很多空白,我从来没有打算把它作为企业开发模式的完整集合。但异步消息传递方面的差距尤为重要,因为我们相信异步消息传递将在企业软件开发中发挥越来越重要的作用,特别是在集成方面。集成很重要,因为应用程序不能彼此隔离。我们需要一些技术,使我们能够采用从未设计为互操作的应用程序并打破“烟囱”,这样我们就可以获得比单个应用程序所能提供的更大的好处。

        There are many gaps in my book, and I never in­ten­ded it to be a com­plete col­lec­tion of pat­terns for en­ter­prise de­vel­op­ment. But the gap on asyn­chron­ous mes­saging is par­tic­u­larly im­port­ant be­cause we be­lieve that asyn­chron­ous mes­saging will play an in­creas­ingly im­port­ant role in en­ter­prise soft­ware de­vel­op­ment, par­tic­u­larly in in­teg­ra­tion. In­teg­ra­tion is im­port­ant be­cause ap­plic­a­tions cannot live isol­ated from each other. We need tech­niques that allow us to take ap­plic­a­tions that were never de­signed to in­ter­op­er­ate and break down the stovepipes so we can gain a greater be­ne­fit than the in­di­vidual ap­plic­a­tions can offer us.

        各种技术都有望解决集成难题。我们都得出结论,消息传递是最有前途的技术。我们面临的挑战是传达如何有效地进行消息传递。其中最大的挑战是消息本质上是异步的,并且您在异步世界中使用的设计方法存在显着差异。

        Vari­ous tech­no­lo­gies have been around that prom­ise to solve the in­teg­ra­tion puzzle. We all con­cluded that mes­saging is the tech­no­logy that car­ries the greatest prom­ise. The chal­lenge we faced was to convey how to do mes­saging ef­fect­ively. The biggest chal­lenge in this is that mes­sages are by their nature asyn­chron­ous, and there are sig­ni­fic­ant dif­fer­ences in the design ap­proaches that you use in an asyn­chron­ous world.

        我没有空间、精力,或者坦率地说没有知识来在企业应用程序架构模式中正确讨论这个主题。但我们想出了一个更好的解决方案来解决这个问题:找到其他有能力的人。我们追捕了格雷戈尔和鲍比,他们接受了挑战。结果就是您将要阅读的书。

        I didn't have space, energy, or frankly the know­ledge to cover this topic prop­erly in Pat­terns of En­ter­prise Ap­plic­a­tion Ar­chi­tec­ture. But we came up with a better solu­tion to this gap: find someone else who could. We hunted down Gregor and Bobby, and they took up the chal­lenge. The result is the book you're about to read.

        我对他们所做的工作感到高兴。如果您已经使用过消息传递系统,那么本书将系统化您和其他人已经通过艰苦的方式学到的大部分知识。如果您即将使用消息传递系统,那么无论您必须使用哪种消息传递技术,本书都将提供非常宝贵的基础。

        I'm de­lighted with the job that they have done. If you've already worked with mes­saging sys­tems, this book will sys­tem­at­ize much of the know­ledge that you and others have already learned the hard way. If you are about to work with mes­saging sys­tems, this book will provide a found­a­tion that will be in­valu­able no matter which mes­saging tech­no­logy you have to work with.

        Martin Fowler

        梅尔罗斯,马萨诸塞州 2003 年

        8 月

        Martin Fowler

        Mel­rose, MA

        August 2003

          前言

          Preface

          这是一本关于使用消息传递进行企业集成的书。它不记录任何特定的技术或产品。相反,它是为使用各种消息传递产品和技术的开发人员和集成商而设计的,例如

          This is a book about en­ter­prise in­teg­ra­tion using mes­saging. It does not doc­u­ment any par­tic­u­lar tech­no­logy or product. Rather, it is de­signed for de­ve­lopers and in­teg­rat­ors using a vari­ety of mes­saging products and tech­no­lo­gies, such as

          • IBM (WebSphere MQ Family)、Microsoft (BizTalk)、TIBCO、WebMethods、SeeBeyond、Vitria 等供应商提供的面向消息的中间件 (MOM) 和 EAI 套件。

          • Mes­sage-ori­ented mid­dle­ware (MOM) and EAI suites offered by vendors such as IBM (Web­Sphere MQ Family), Mi­crosoft (BizTalk), TIBCO, Web­Meth­ods, See­Bey­ond, Vitria, and others.

          • Java 消息服务 (JMS) 实现已合并到商业和开源 J2EE 应用程序服务器以及独立产品中。

          • Java Mes­sage Ser­vice (JMS) im­ple­ment­a­tions in­cor­por­ated into com­mer­cial and open source J2EE ap­plic­a­tion serv­ers as well as stan­dalone products.

          • Microsoft 的消息队列 (MSMQ),可通过多个 API 访问,包括 Microsoft .NET 中的 System.Messaging 库。

          • Mi­crosoft's Mes­sage Queuing (MSMQ), ac­cess­ible through sev­eral APIs, in­clud­ing the System.Mes­saging lib­rar­ies in Mi­crosoft .NET.

          • 支持异步 Web 服务(例如 WS-ReliableMessaging)和相关 API(例如 Sun Microsystems 的 Java API for XML Messaging (JAXM) 或 Microsoft 的 Web Services Extensions (WSE))的新兴 Web 服务标准。

          • Emer­ging Web ser­vices stand­ards that sup­port asyn­chron­ous Web ser­vices (for ex­ample, WS-Re­li­ableMes­saging) and the as­so­ci­ated APIs such as Sun Mi­crosys­tems' Java API for XML Mes­saging (JAXM) or Mi­crosoft's Web Ser­vices Ex­ten­sions (WSE).

          企业集成不仅仅是创建具有分布式网络的单个应用程序层架构,使单个应用程序能够分布在多台计算机上。分布式应用程序中的一层无法自行运行,而集成应用程序是独立的程序,每个程序都可以自行运行,但通过以松散耦合的方式相互协调来发挥作用。消息传递使多个应用程序能够使用“发送后忘记”的方法通过网络交换数据或命令。这允许呼叫者发送信息并在消息系统传输信息的同时立即继续其他工作。或者,稍后可以通过回调通知调用者结果。异步调用和回调可以使设计比同步方法更复杂,但是异步调用可以重试直到成功,这使得通信更加可靠。异步消息传递还具有其他一些优势,例如请求限制和负载平衡。

          En­ter­prise in­teg­ra­tion goes beyond cre­at­ing a single ap­plic­a­tion with a dis­trib­uted n-tier ar­chi­tec­ture, which en­ables a single ap­plic­a­tion to be dis­trib­uted across sev­eral com­puters. Whereas one tier in a dis­trib­uted ap­plic­a­tion cannot run by itself, in­teg­rated ap­plic­a­tions are in­de­pend­ent pro­grams that can each run by them­selves, yet that func­tion by co­ordin­at­ing with each other in a loosely coupled way. Mes­saging en­ables mul­tiple ap­plic­a­tions to ex­change data or com­mands across the net­work using a "send and forget" ap­proach. This allows the caller to send the in­form­a­tion and im­me­di­ately go on to other work while the in­form­a­tion is trans­mit­ted by the mes­saging system. Op­tion­ally, the caller can later be no­ti­fied of the result through a call­back. Asyn­chron­ous calls and call­backs can make a design more com­plex than a syn­chron­ous ap­proach, but an asyn­chron­ous call can be re­tried until it suc­ceeds, which makes the com­mu­nic­a­tion much more re­li­able. Asyn­chron­ous mes­saging also en­ables sev­eral other ad­vant­ages, such as throt­tling of re­quests and load bal­an­cing.

            谁应该读这本书

            Who Should Read This Book

            本书旨在帮助应用程序开发人员和系统集成商使用面向消息的集成工具连接应用程序:

            This book is de­signed to help ap­plic­a­tion de­ve­lopers and system in­teg­rat­ors con­nect ap­plic­a­tions using mes­sage-ori­ented in­teg­ra­tion tools:

            • 设计和构建需要与其他应用程序集成的复杂企业应用程序的应用程序架构师和开发人员。我们假设您正在使用现代企业应用程序平台(例如 Java 2 平台企业版 (J2EE) 或 Microsoft .NET Framework)开发应用程序。本书将帮助您将应用程序连接到消息传递层并与其他应用程序交换信息。本书重点关注应用程序的集成,而不是构建应用程序;为此,我们向您推荐Martin Fowler 的《企业应用程序架构模式》 。

            • Ap­plic­a­tion ar­chi­tects and de­ve­lopers who design and build com­plex en­ter­prise ap­plic­a­tions that need to in­teg­rate with other ap­plic­a­tions. We assume that you're de­vel­op­ing your ap­plic­a­tions using a modern en­ter­prise ap­plic­a­tion plat­form such as the Java 2 Plat­form, En­ter­prise Edi­tion (J2EE), or the Mi­crosoft .NET Frame­work. This book will help you con­nect the ap­plic­a­tion to a mes­saging layer and ex­change in­form­a­tion with other ap­plic­a­tions. This book fo­cuses on the in­teg­ra­tion of ap­plic­a­tions, not on build­ing ap­plic­a­tions; for that, we refer you to Pat­terns of En­ter­prise Ap­plic­a­tion Ar­chi­tec­ture by Martin Fowler.

            • 设计和构建连接打包或自定义应用程序的集成解决方案的集成架构师和开发人员。本组中的大多数读者都具有使用 IBM WebSphere MQ、TIBCO、WebMethods、SeeBeyond 或 Vitria 等众多商业集成工具之一的经验,这些工具结合了本书中介绍的许多模式。本书可帮助您理解基本概念,并使用独立于供应商的词汇做出自信的设计决策。

            • In­teg­ra­tion ar­chi­tects and de­ve­lopers who design and build in­teg­ra­tion solu­tions con­nect­ing pack­aged or custom ap­plic­a­tions. Most read­ers in this group will have ex­per­i­ence with one of the many com­mer­cial in­teg­ra­tion tools like IBM Web­Sphere MQ, TIBCO, Web­Meth­ods, See­Bey­ond, or Vitria, which in­cor­por­ate many of the pat­terns presen­ted in this book. This book helps you un­der­stand the un­der­ly­ing con­cepts and make con­fid­ent design de­cisions using a vendor-in­de­pend­ent vocab­u­lary.

            • 企业架构师必须维护企业中软件和硬件资产的“全局”视图。本书提供了一致的词汇和图形符号来描述可能跨越多种技术或单点解决方案的大规模集成解决方案。这种语言也是企业架构师与集成和应用程序架构师和开发人员之间有效沟通的关键推动者。

            • En­ter­prise ar­chi­tects who have to main­tain the "big pic­ture" view of the soft­ware and hard­ware assets in an en­ter­prise. This book presents a con­sist­ent vocab­u­lary and graph­ical nota­tion to de­scribe large-scale in­teg­ra­tion solu­tions that may span many tech­no­lo­gies or point solu­tions. This lan­guage is also a key en­a­bler for ef­fi­cient com­mu­nic­a­tion between the en­ter­prise ar­chi­tect and the in­teg­ra­tion and ap­plic­a­tion ar­chi­tects and de­ve­lopers.

              你将学到什么

              What You Will Learn

              本书并不试图为企业应用程序集成提供商业案例;而是试图为企业应用程序集成提供商业案例。重点是如何使其发挥作用。您将通过了解以下内容来学习如何集成企业应用程序:

              This book does not at­tempt to make a busi­ness case for en­ter­prise ap­plic­a­tion in­teg­ra­tion; the focus is on how to make it work. You will learn how to in­teg­rate en­ter­prise ap­plic­a­tions by un­der­stand­ing the fol­low­ing:

              • 与其他集成技术相比,异步消息传递的优点和局限性。

              • The ad­vant­ages and lim­it­a­tions of asyn­chron­ous mes­saging as com­pared to other in­teg­ra­tion tech­niques.

              • 如何确定应用程序需要的消息通道,如何控制多个消费者是否可以接收同一消息,以及如何处理无效消息。

              • How to de­term­ine the mes­sage chan­nels your ap­plic­a­tions will need, how to con­trol whether mul­tiple con­sumers can re­ceive the same mes­sage, and how to handle in­valid mes­sages.

              • 何时发送消息、消息应包含什么内容以及如何使用特殊消息属性。

              • When to send a mes­sage, what it should con­tain, and how to use spe­cial mes­sage prop­er­ties.

              • 即使发送者不知道最终目的地在哪里,如何将消息路由到最终目的地。

              • How to route a mes­sage to its ul­ti­mate des­tin­a­tion even when the sender does not know where that is.

              • 当发送者和接收者不同意通用格式时如何转换消息。

              • How to con­vert mes­sages when the sender and re­ceiver do not agree on a common format.

              • 如何设计将应用程序连接到消息传递系统的代码。

              • How to design the code that con­nects an ap­plic­a­tion to the mes­saging system.

              • 作为企业的一部分使用后如何管理和监控消息传递系统。

              • How to manage and mon­itor a mes­saging system once it's in use as part of the en­ter­prise.

                本书未涵盖的内容

                What This Book Does Not Cover

                我们认为,任何标题中带有“企业”一词的书都可能属于以下三类之一。首先,本书可能试图涵盖主题的全部内容,但被迫停止提供有关如何实施实际解决方案的详细指导。其次,这本书可能会提供有关开发实际解决方案的具体实践指导,但被迫限制其所涉及的主题领域的范围。第三,这本书可能试图做到这两点,但很可能永远不会完成,否则出版得太晚,以至于变得无关紧要。我们选择了第二个选择,并希望创建一本能够帮助人们创建更好的集成解决方案的书,尽管我们不得不限制这本书的范围。我们很想讨论但为了不落入第三类陷阱而必须排除的主题包括安全性、复杂数据映射、工作流、规则引擎、可扩展性和鲁棒性以及分布式事务处理(XA、Tuxedo 和喜欢)。我们选择异步消息传递作为本书的重点,因为它充满了有趣的设计问题和权衡,并从各个集成供应商提供的许多实现中提供了清晰的抽象。

                We be­lieve that any book sport­ing the word "en­ter­prise" in the title is likely to fall into one of three cat­egor­ies. First, the book might at­tempt to cover the whole breadth of the sub­ject matter but is forced to stop short of de­tailed guid­ance on how to im­ple­ment actual solu­tions. Second, the book might provide spe­cific hands-on guid­ance on the de­vel­op­ment of actual solu­tions but is forced to con­strain the scope of the sub­ject area it ad­dresses. Third, the book might at­tempt to do both but is likely never to be fin­ished or else to be pub­lished so late as to be ir­rel­ev­ant. We opted for the second choice and hope­fully cre­ated a book that helps people create better in­teg­ra­tion solu­tions even though we had to limit the scope of the book. Topics that we would have loved to dis­cuss but had to ex­clude in order not to fall into the cat­egory-three trap in­clude se­cur­ity, com­plex data map­ping, work­flow, rule en­gines, scalab­il­ity and ro­bust­ness, and dis­trib­uted trans­ac­tion pro­cess­ing (XA, Tuxedo, and the like). We chose asyn­chron­ous mes­saging as the em­phasis for this book be­cause it is full of in­ter­est­ing design issues and trade-offs, and provides a clean ab­strac­tion from the many im­ple­ment­a­tions provided by vari­ous in­teg­ra­tion vendors.

                本书也不是关于特定消息传递或中间件技术的教程。为了强调本书中提出的概念的广泛适用性,我们提供了基于许多不同技术的示例,例如 JMS、MSMQ、TIBCO、BizTalk 和 XSL。然而,我们关注的是设计决策和权衡,而不是工具的细节。如果您有兴趣了解有关这些特定技术的更多信息,请参阅参考书目中引用的书籍之一或众多在线资源之一。

                This book is also not a tu­torial on a spe­cific mes­saging or mid­dle­ware tech­no­logy. To high­light the wide ap­plic­ab­il­ity of the con­cepts presen­ted in this book, we in­cluded ex­amples based on a number of dif­fer­ent tech­no­lo­gies, such as JMS, MSMQ, TIBCO, BizTalk, and XSL. How­ever, we focus on the design de­cisions and trade-offs as op­posed to the spe­cif­ics of the tool. If you are in­ter­ested in learn­ing more about any of these spe­cific tech­no­lo­gies, please refer to one of the books ref­er­enced in the bib­li­o­graphy or to one of the many online re­sources.

                  本书的结构

                  How This Book Is Organized

                  正如标题所示,本书的大部分内容由模式集合组成。在没有简单的“一刀切”答案的领域,例如应用程序体系结构、面向对象的设计或基于异步消息传递体系结构的集成解决方案,模式是一种行之有效的捕获专家知识的方法。

                  As the title sug­gests, the ma­jor­ity of this book con­sists of a col­lec­tion of pat­terns. Pat­terns are a proven way to cap­ture ex­perts' know­ledge in fields where there are no simple "one size fits all" an­swers, such as ap­plic­a­tion ar­chi­tec­ture, object-ori­ented design, or in­teg­ra­tion solu­tions based on asyn­chron­ous mes­saging ar­chi­tec­tures.

                  每个模式都会提出一个特定的设计问题,讨论围绕该问题的考虑因素,并提出一个平衡各种力量或驱动因素的优雅解决方案。在大多数情况下,解决方案并不是首先想到的方法,而是随着时间的推移在实际使用中不断演变的方法。因此,每种模式都包含了高级集成开发人员和架构师通过反复构建解决方案并从错误中学习而获得的经验基础。这意味着我们并没有“发明”本书中的模式;而是我们“发明”了本书中的模式。模式不是发明的,而是在该领域的实际实践中发现和观察到的。

                  Each pat­tern poses a spe­cific design prob­lem, dis­cusses the con­sid­er­a­tions sur­round­ing the prob­lem, and presents an el­eg­ant solu­tion that bal­ances the vari­ous forces or drivers. In most cases, the solu­tion is not the first ap­proach that comes to mind, but one that has evolved through actual use over time. As a result, each pat­tern in­cor­por­ates the ex­per­i­ence base that senior in­teg­ra­tion de­ve­lopers and ar­chi­tects have gained by re­peatedly build­ing solu­tions and learn­ing from their mis­takes. This im­plies that we did not "invent" the pat­terns in this book; pat­terns are not in­ven­ted, but rather dis­covered and ob­served from actual prac­tice in the field.

                  因为模式是从实践者的实际使用中收获的,所以如果您已经使用企业集成工具和异步消息传递架构有一段时间了,那么本书中的许多模式您可能会感到熟悉。然而,即使您已经认识了其中的大部分模式,回顾本书仍然有价值。本书应该验证您对如何使用消息传递来之不易的理解,同时记录您可能不知道的解决方案及其之间关系的详细信息。它还为您提供综合参考,帮助您有效地将知识传授给经验不足的同事。最后,

                  Be­cause pat­terns are har­ves­ted from prac­ti­tion­ers' actual use, chances are that if you have been work­ing with en­ter­prise in­teg­ra­tion tools and asyn­chron­ous mes­saging ar­chi­tec­tures for some time, many of the pat­terns in this book will seem fa­mil­iar to you. Yet, even if you already re­cog­nize most of these pat­terns, there is still value in re­view­ing this book. This book should val­id­ate your hard-earned un­der­stand­ing of how to use mes­saging while doc­u­ment­ing de­tails of the solu­tions and re­la­tion­ships between them of which you might not have been aware. It also gives you a con­sol­id­ated ref­er­ence to help you pass your know­ledge ef­fect­ively to less-ex­per­i­enced col­leagues. Fi­nally, the pat­tern names give you a common vocab­u­lary to ef­fi­ciently dis­cuss in­teg­ra­tion design al­tern­at­ives with your peers.

                  本书中的模式适用于各种编程语言和平台。这意味着模式不是剪切和粘贴的代码片段,但您必须针对特定环境实现模式。为了使这种转换更容易,我们添加了各种示例,这些示例展示了使用流行技术(例如 JMS、MSMQ、TIBCO、BizTalk、XSL 等)实现模式的不同方法。我们还提供了一些较大的示例来演示多个模式如何一起发挥作用以形成一个有凝聚力的解决方案。

                  The pat­terns in this book apply to a vari­ety of pro­gram­ming lan­guages and plat­forms. This means that a pat­tern is not a cut-and-paste snip­pet of code, but you have to real­ize a pat­tern to your spe­cific en­vir­on­ment. To make this trans­la­tion easier, we added a vari­ety of ex­amples that show dif­fer­ent ways of im­ple­ment­ing pat­terns using pop­u­lar tech­no­lo­gies such as JMS, MSMQ, TIBCO, BizTalk, XSL, and others. We also in­cluded a few larger ex­amples to demon­strate how mul­tiple pat­terns play to­gether to form a co­hes­ive solu­tion.

                  使用异步消息传递架构集成多个应用程序是一个充满挑战且有趣的领域。我们希望您能像我们编写这本书一样喜欢阅读这本书。

                  In­teg­rat­ing mul­tiple ap­plic­a­tions using an asyn­chron­ous mes­saging ar­chi­tec­ture is a chal­len­ging and in­ter­est­ing field. We hope you enjoy read­ing this book as much as we did writ­ing it.

                    关于封面图片

                    About the Cover Picture

                    马丁·福勒签名系列书籍的共同主题是桥梁的图片。从某种意义上说,我们很幸运,因为什么主题更适合一本关于集成的书呢?数千年来,桥梁帮助连接来自不同海岸、山区和路边的人们。

                    The common theme for books in the Martin Fowler Sig­na­ture Series is a pic­ture of a bridge. In some sense we lucked out, be­cause what theme would make a better match for a book on in­teg­ra­tion? For thou­sands of years, bridges have helped con­nect people from dif­fer­ent shores, moun­tains, and sides of the road.

                    我们选择了日本大阪住吉大社的太鼓桥的照片,因为它简单而优雅。作为供奉水手守护神的神社,最初建在水边。有趣的是,填海造地已将水推开,因此今天的神社矗立在内陆近三英里处。新年伊始,约有三百万人参拜这座神社。

                    We se­lec­ted a pic­ture of the Taiko-bashi Bridge at the Sum­iy­oshi-taisha Shrine in Osaka, Japan, for its simple el­eg­ance and beauty. As a Shinto shrine ded­ic­ated to the guard­ian deity for sail­ors, it was ori­gin­ally erec­ted next to the water. In­ter­est­ingly, land re­clam­a­tion has pushed the water away so that the shrine today stands almost three miles inland. Some three mil­lion people visit this shrine at the be­gin­ning of a new year.

                    Gregor Hohpe

                    加利福尼亚





                    旧金山 Bobby Woolf 北卡罗来纳州



                    罗利2003 年 9 月

                    www.enterpriseintegrationpatterns.com

                    Gregor Hohpe

                    San Fran­cisco, Cali­for­nia



                    Bobby Woolf

                    Raleigh, North Car­o­lina



                    Septem­ber 2003

                    www.en­ter­pri­sein­teg­ra­tion­pat­terns.com

                    卡尔·萨根博士的先锋牌匾

                    The Pi­on­eer Plaque by Dr. Carl Sagan

                    给外星生命形式的信息。

                    A mes­sage to ex­tra­ter­re­strial life forms.

                    图形/fm01inf01.gif

                      致谢

                      Acknowledgments

                      与大多数书籍一样,《企业集成模式》已经酝酿了很长时间。撰写基于消息的集成模式的想法可以追溯到 2001 年夏天,当时 Martin Fowler 正在研究企业应用程序架构模式( EAA 的 P) 。当时,凯尔·布朗 (Kyle Brown) 突然意识到,虽然EAA 的 P讨论了很多如何创建应用程序,仅简单介绍了如何集成它们。这个想法是马丁和凯尔之间一系列会议的起点,其中包括雷切尔·雷尼茨、约翰·克鲁皮和马克·韦策尔。Bobby 于 2001 年秋季加入了这些讨论,Gregor 于 2002 年初加入。第二年夏天,该小组提交了两篇论文供程序模式语言 (PLoP) 会议审查,其中一篇由 Bobby 和 Kyle 共同撰写,另一篇由 Gregor 撰写。会议结束后,凯尔和马丁重新专注于他们自己的书籍项目,而格雷戈尔和鲍比合并了他们的论文,形成了这本书的基础。同时,www.enterpriseintegrationpatterns.com网站上线后,世界各地的集成架构师和开发人员都可以参与内容的快速发展。在写这本书的过程中,格雷戈尔和鲍比邀请了贡献者参与这本书的创作。凯尔提出最初的想法大约两年后,最终的手稿到达了出版商。

                      Like most books, En­ter­prise In­teg­ra­tion Pat­terns has been a long time in the making. The idea of writ­ing about mes­sage-based in­teg­ra­tion pat­terns dates back to the summer of 2001 when Martin Fowler was work­ing on Pat­terns of En­ter­prise Ap­plic­a­tion Ar­chi­tec­ture (P of EAA). At that time, it struck Kyle Brown that while P of EAA talked a lot about how to create ap­plic­a­tions, it touches only briefly on how to in­teg­rate them. This idea was the start­ing point for a series of meet­ings between Martin and Kyle that also in­cluded Rachel Rein­itz, John Crupi, and Mark Weitzel. Bobby joined these dis­cus­sions in the fall of 2001, fol­lowed by Gregor in early 2002. The fol­low­ing summer the group sub­mit­ted two papers for review at the Pat­tern Lan­guages of Pro­grams (PLoP) con­fer­ence, one au­thored jointly by Bobby and Kyle and the other by Gregor. After the con­fer­ence, Kyle and Martin re­fo­cused on their own book pro­jects while Gregor and Bobby merged their papers to form the basis for the book. At the same time, the www.en­ter­pri­sein­teg­ra­tion­pat­terns.com site went live to allow in­teg­ra­tion ar­chi­tects and de­ve­lopers around the world to par­ti­cip­ate in the rapid evol­u­tion of the con­tent. As they worked on the book, Gregor and Bobby in­vited con­trib­ut­ors to par­ti­cip­ate in the cre­ation of the book. About two years after Kyle's ori­ginal idea, the final ma­nu­script ar­rived at the pub­lisher.

                      本书是社区众多人员共同努力的成果。许多同事和朋友(其中许多人是我们在本书中认识的)提供了示例想法,确保了技术内容的正确性,并给了我们急需的反馈和批评。他们的意见极大地影响了本书的最终形式和内容。我们很高兴承认他们的贡献并对他们的努力表示赞赏。

                      This book is the result of a com­munity effort in­volving a great number of people. Many col­leagues and friends (many of whom we met through the book effort) provided ideas for ex­amples, en­sured the cor­rect­ness of the tech­nical con­tent, and gave us much needed feed­back and cri­ti­cism. Their input has greatly in­flu­enced the final form and con­tent of the book. It is a pleas­ure for us to ac­know­ledge their con­tri­bu­tions and ex­press our ap­pre­ci­ation for their ef­forts.

                      凯尔·布​​朗和马丁·福勒为本书奠定了基础,值得特别提及。如果不是 Martin 撰写了EAA 的 P,并且 Kyle 组建了一个小组来讨论消息传递模式以补充 Martin 的书,这本书可能永远不会被写出来。

                      Kyle Brown and Martin Fowler de­serve spe­cial men­tion for laying the found­a­tion for this book. This book might have never been writ­ten were it not for Martin's writ­ing P of EAA and Kyle's form­ing a group to dis­cuss mes­saging pat­terns to com­ple­ment Martin's book.

                      我们很幸运有几位贡献者撰写了本书的重要部分:Conrad F. D'Cruz、Sean Neville、Michael J. Rettig 和 Jonathan Simon。他们的章节对这些模式如何在实践中发挥作用提供了额外的视角,从而使本书更加完整。

                      We were for­tu­nate to have sev­eral con­trib­ut­ors who au­thored sig­ni­fic­ant por­tions of the book: Conrad F. D'Cruz, Sean Neville, Mi­chael J. Rettig, and Jonathan Simon. Their chapters round out the book with ad­di­tional per­spect­ives on how the pat­terns work in prac­tice.

                      PLoP 2002 会议上我们的作家研讨会参与者是第一批对材料提供实质性反馈的人,帮助我们朝着正确的方向前进:Ali Arsanjani、Kyle Brown、John Crupi、Eric Evans、Martin Fowler、Brian Marick、托比·萨弗、乔纳森·西蒙、比尔·特鲁德尔和马雷克·沃卡奇。

                      Our writers' work­shop par­ti­cipants at the PLoP 2002 con­fer­ence were the first people to provide sub­stan­tial feed­back on the ma­ter­ial, help­ing to get us going in the right dir­ec­tion: Ali Arsan­jani, Kyle Brown, John Crupi, Eric Evans, Martin Fowler, Brian Marick, Toby Sarver, Jonathan Simon, Bill Trudell, and Marek Vokac.

                      我们要感谢我们的审稿团队,他们花时间阅读了草稿材料,并向我们提供了宝贵的反馈和建议:

                      We would like to thank our team of re­view­ers who took the time to read through the draft ma­ter­ial and provided us with in­valu­able feed­back and sug­ges­tions:

                      理查德·赫尔姆

                      卢克·霍曼

                      德拉戈斯·马诺莱斯库

                      大卫·赖斯

                      Russ Rufer 和硅谷模式小组

                      马修·肖特

                      Richard Helm

                      Luke Hohmann

                      Dragos Man­olescu

                      David Rice

                      Russ Rufer and the Sil­icon Valley Pat­terns Group

                      Mat­thew Short

                      特别感谢 Russ 在硅谷模式小组中参与起草本书草稿。我们要感谢以下成员的努力:Robert Benson、Tracy Bialik、Jeffrey Blake、Azad Bolour、John Brewer、Bob Evans、Andy Farlie、Jeff Glaza、Phil Goodwin、Alan Harriman、Ken Hejmanowski、Deborah Kaddah、Rituraj Kirti 、Jan Looney、Chris Lopez、Jerry Louis、马道洪、Jeff Miller、Stilian Pandev、John Parello、Hema Pillay、Russ Rufer、Rich Smith、Carol Thistlethwaite、Debbie Utley、Walter Vannini、David Vydra 和 Ted Young。

                      Spe­cial thanks go to Russ for work­shop­ping the book draft in the Sil­icon Valley Pat­terns Group. We would like to thank the fol­low­ing mem­bers for their ef­forts: Robert Benson, Tracy Bialik, Jef­frey Blake, Azad Bolour, John Brewer, Bob Evans, Andy Farlie, Jeff Glaza, Phil Good­win, Alan Har­ri­man, Ken Hej­manowski, De­borah Kaddah, Rituraj Kirti, Jan Looney, Chris Lopez, Jerry Louis, Tao-hung Ma, Jeff Miller, Stilian Pandev, John Parello, Hema Pillay, Russ Rufer, Rich Smith, Carol Thistleth­waite, Debbie Utley, Walter Van­nini, David Vydra, and Ted Young.

                      我们的公共电子邮件讨论列表允许在www.enterpriseintegrationpatterns.com上发现材料的人们插话并分享他们的想法和想法。Bill Trudell 作为邮件列表最活跃的贡献者获得了特殊荣誉。其他活跃海报包括 Venkateshwar Bommineni、Duncan Cragg、John Crupi、Fokko Degenaar、Shailesh Gosavi、Christian Hall、Ralph Johnson、Paul Julius、Orjan Lundberg、Dragos Manolescu、Rob Mee、Srikanth Narasimhan、Sean Neville、Rob Patton、Kirk Pepperdine、Matthew普赖尔、索米克·拉哈、迈克尔·雷蒂格、弗兰克·绍尔、乔纳森·西蒙、费德里科·斯皮纳齐、兰迪·斯塔福德、马雷克·沃卡奇、乔·沃尔恩斯和马克·韦策尔。

                      Our public e-mail dis­cus­sion list al­lowed people who dis­covered the ma­ter­ial on www.en­ter­pri­sein­teg­ra­tion­pat­terns.com to chime in and share their thoughts and ideas. Spe­cial honors go to Bill Trudell as the most active con­trib­utor to the mail­ing list. Other active posters in­cluded Ven­katesh­war Bom­min­eni, Duncan Cragg, John Crupi, Fokko De­gen­aar, Shailesh Gosavi, Chris­tian Hall, Ralph John­son, Paul Julius, Orjan Lun­d­berg, Dragos Man­olescu, Rob Mee, Srik­anth Narasim­han, Sean Neville, Rob Patton, Kirk Pep­perdine, Mat­thew Pryor, Somik Raha, Mi­chael Rettig, Frank Sauer, Jonathan Simon, Fe­d­erico Spinazzi, Randy Stafford, Marek Vokac, Joe Walnes, and Mark Weitzel.

                      我们感谢马丁·福勒 (Martin Fowler) 招待我们观看他的签名系列节目。马丁的认可给了我们完成这项工作所需的信心和精力。

                      We thank Martin Fowler for host­ing us in his sig­na­ture series. Martin's en­dorse­ment gave us con­fid­ence and the energy re­quired to com­plete this work.

                      我们感谢约翰·克鲁皮为我们的书撰写前言。他从一开始就观察了这本书的形成,并一直耐心指导,但从未失去幽默感。

                      We thank John Crupi for writ­ing the fore­word for our book. He has ob­served the book's form­a­tion from the be­gin­ning and has been a pa­tient guide all along without ever losing his sense of humor.

                      最后,我们非常感谢 Addison-Wesley 的编辑和制作团队,他们由我们的主编辑 Mike Hendrickson 领导,包括我们的制作协调员 Amy Fleischer;我们的项目经理 Kim Arney Mulcahy;我们的文案编辑 Carol J. Lallier;我们的校对员 Rebecca Rider;我们的索引员 Sharon Hilgenberg;以及杰奎琳·杜赛特、约翰·富勒和伯纳德·加夫尼。

                      Fi­nally, we owe a great deal to the edit­ing and pro­duc­tion team at Ad­dison-Wesley, led by our chief editor, Mike Hendrick­son, and in­clud­ing our pro­duc­tion co­ordin­ator, Amy Fleis­cher; our pro­ject man­ager, Kim Arney Mul­cahy; our copy­ed­itor, Carol J. Lal­lier; our proofreader, Re­becca Rider; our in­dexer, Sharon Hil­gen­berg; as well as Jac­quelyn Doucette, John Fuller, and Bern­ard Gaffney.

                      我们可能遗漏了一些名字,并且没有给予每个人应有的荣誉,我们深表歉意。但对于所有列出的和未列出的帮助使本书变得更好的人,感谢你们的帮助。我们希望您能像我们一样为这本书感到自豪。

                      We've likely missed some names and not given every­one the credit they de­serve, and we apo­lo­gize. But to every­one listed and not listed who helped make this book better, thank you for all your help. We hope you can be as proud of this book as we are.

                        介绍

                        Introduction

                        有趣的应用程序很少是孤立存在的。无论您的销售应用程序必须与库存应用程序交互,您的采购应用程序必须连接到拍卖站点,还是您的 PDA 日历必须与公司日历服务器同步,似乎任何应用程序都可以通过与其他应用程序集成来变得更好。

                        In­ter­est­ing ap­plic­a­tions rarely live in isol­a­tion. Whether your sales ap­plic­a­tion must in­ter­face with your in­vent­ory ap­plic­a­tion, your pro­cure­ment ap­plic­a­tion must con­nect to an auc­tion site, or your PDA's cal­en­dar must syn­chron­ize with the cor­por­ate cal­en­dar server, it seems that any ap­plic­a­tion can be made better by in­teg­rat­ing it with other ap­plic­a­tions.

                        所有集成解决方案都必须应对一些基本挑战:

                        All in­teg­ra­tion solu­tions have to deal with a few fun­da­mental chal­lenges:

                        • 网络不可靠。集成解决方案必须通过网络将数据从一台计算机传输到另一台计算机。与在单台计算机上运行的进程相比,分布式计算必须准备好处理更多的可能问题。通常,要集成的两个系统相隔大洲,它们之间的数据必须通过电话线、LAN 网段、路由器、交换机、公共网络和卫星链路传输。每个步骤都可能导致延迟或中断。

                        • Net­works are un­re­li­able. In­teg­ra­tion solu­tions have to trans­port data from one com­puter to an­other across net­works. Com­pared to a pro­cess run­ning on a single com­puter, dis­trib­uted com­put­ing has to be pre­pared to deal with a much larger set of pos­sible prob­lems. Often, two sys­tems to be in­teg­rated are sep­ar­ated by con­tin­ents, and data between them has to travel through phone lines, LAN seg­ments, routers, switches, public net­works, and satel­lite links. Each step can cause delays or in­ter­rup­tions.

                        • 网络很慢。通过网络发送数据比进行本地方法调用慢多个数量级。以与处理单个应用程序相同的方式设计广泛分布的解决方案可能会产生灾难性的性能影响。

                        • Net­works are slow. Send­ing data across a net­work is mul­tiple orders of mag­nitude slower than making a local method call. Design­ing a widely dis­trib­uted solu­tion the same way you would ap­proach a single ap­plic­a­tion could have dis­as­trous per­form­ance im­plic­a­tions.

                        • 任何两个应用程序都是不同的。集成解决方案需要在使用不同编程语言、操作平台和数据格式的系统之间传输信息。集成解决方案必须能够与所有这些不同的技术交互。

                        • Any two ap­plic­a­tions are dif­fer­ent. In­teg­ra­tion solu­tions need to trans­mit in­form­a­tion between sys­tems that use dif­fer­ent pro­gram­ming lan­guages, op­er­at­ing plat­forms, and data formats. An in­teg­ra­tion solu­tion must be able to in­ter­face with all these dif­fer­ent tech­no­lo­gies.

                        • 改变是不可避免的。应用程序会随着时间的推移而变化。集成解决方案必须跟上所连接的应用程序的变化。集成解决方案很容易陷入变化的雪崩效应中,如果一个系统发生变化,所有其他系统都可能受到影响。集成解决方案需要通过在应用程序之间使用松散耦合来最大限度地减少从一个系统到另一个系统的依赖性。

                        • Change is in­ev­it­able. Ap­plic­a­tions change over time. An in­teg­ra­tion solu­tion has to keep pace with changes in the ap­plic­a­tions it con­nects. In­teg­ra­tion solu­tions can easily get caught in an ava­lanche effect of changesif one system changes, all other sys­tems may be af­fected. An in­teg­ra­tion solu­tion needs to min­im­ize the de­pend­en­cies from one system to an­other by using loose coup­ling between ap­plic­a­tions.

                        随着时间的推移,开发人员通过四种主要方法克服了这些挑战:

                        Over time, de­ve­lopers have over­come these chal­lenges with four main ap­proaches:

                        1. 文件传输 一个应用程序需要就文件名和位置、文件格式、写入和读取时间以及谁将删除文件达成一致。

                        2. File Trans­fer One ap­plic­a­tion writes a file that an­other later reads. The ap­plic­a­tions need to agree on the fi­le­name and loc­a­tion, the format of the file, the timing of when it will be writ­ten and read, and who will delete the file.

                        3. 共享数据库 多个由于不存在重复的数据存储,因此无需将数据从一个应用程序传输到另一应用程序。

                        4. Shared Data­base Mul­tiple ap­plic­a­tions share the same data­base schema, loc­ated in a single phys­ical data­base. Be­cause there is no du­plic­ate data stor­age, no data has to be trans­ferred from one ap­plic­a­tion to the other.

                        5. 远程过程调用 一个应用程序公开其某些功能,以便其他应用程序可以将其作为远程过程进行远程访问。通信是实时同步发生的。

                        6. Remote Pro­ced­ure In­voc­a­tion One ap­plic­a­tion ex­poses some of its func­tion­al­ity so that it can be ac­cessed re­motely by other ap­plic­a­tions as a remote pro­ced­ure. The com­mu­nic­a­tion occurs in real time and syn­chron­ously.

                        7. 消息传递 一个其他应用程序可以稍后从通道读取消息。应用程序必须就通道以及消息格式达成一致。通信是异步的。

                        8. Mes­saging One ap­plic­a­tion pub­lishes a mes­sage to a common mes­sage chan­nel. Other ap­plic­a­tions can read the mes­sage from the chan­nel at a later time. The ap­plic­a­tions must agree on a chan­nel as well as on the format of the mes­sage. The com­mu­nic­a­tion is asyn­chron­ous.

                        虽然所有四种方法基本上解决相同的问题,但每种风格都有其独特的优点和缺点。事实上,应用程序可以使用多种样式进行集成,以便每个集成点都可以利用最适合它的样式。

                        While all four ap­proaches solve es­sen­tially the same prob­lem, each style has its dis­tinct ad­vant­ages and dis­ad­vant­ages. In fact, ap­plic­a­tions may in­teg­rate using mul­tiple styles such that each point of in­teg­ra­tion takes ad­vant­age of the style that suits it best.

                          什么是消息传递?

                          What Is Messaging?

                          本书讲述如何使用消息传递来集成应用程序。了解消息传递功能的一个简单方法是考虑电话系统。电话呼叫是一种同步通信形式。仅当我拨打电话时对方有空时,我才能与对方通信。另一方面,语音邮件允许异步通信。使用语音信箱,当接收者没有接听时,呼叫者可以给他留言;之后,接收者(在他方便的时候)可以收听在他的邮箱中排队的消息。语音邮件使呼叫者现在可以留言,以便接收者稍后可以收听,这比尝试让呼叫者和接收者同时通话要容易得多。语音邮件将(至少部分)电话捆绑到一条消息中,并将其排队以供以后使用;这本质上就是消息传递的工作原理。

                          This book is about how to use mes­saging to in­teg­rate ap­plic­a­tions. A simple way to un­der­stand what mes­saging does is to con­sider the tele­phone system. A tele­phone call is a syn­chron­ous form of com­mu­nic­a­tion. I can com­mu­nic­ate with the other party only if the other party is avail­able at the time I place the call. Voice mail, on the other hand, allows asyn­chron­ous com­mu­nic­a­tion. With voice mail, when the re­ceiver does not answer, the caller can leave him a mes­sage; later, the re­ceiver (at his con­veni­ence) can listen to the mes­sages queued in his mail­box. Voice mail en­ables the caller to leave a mes­sage now so that the re­ceiver can listen to it later, which is much easier than trying to get the caller and the re­ceiver on the phone at the same time. Voice mail bundles (at least part of) a phone call into a mes­sage and queues it for later con­sump­tion; this is es­sen­tially how mes­saging works.

                          消息传递是一种能够实现高速、异步、程序到程序通信并可靠交付的技术。程序通过向彼此发送称为消息的数据包来进行通信。Channel ,也称为队列,是连接程序和传递消息的逻辑路径。通道的行为类似于消息的集合或数组,但可以在多台计算机之间神奇地共享,并且可以由多个应用程序同时使用。发送生产者是通过将消息写入通道来发送消息的程序。接收者消费者是一个通过从通道读取(并删除)消息来接收消息的程序。

                          Mes­saging is a tech­no­logy that en­ables high-speed, asyn­chron­ous, pro­gram-to-pro­gram com­mu­nic­a­tion with re­li­able de­liv­ery. Pro­grams com­mu­nic­ate by send­ing pack­ets of data called mes­sages to each other. Chan­nels, also known as queues, are lo­gical path­ways that con­nect the pro­grams and convey mes­sages. A chan­nel be­haves like a col­lec­tion or array of mes­sages, but one that is ma­gic­ally shared across mul­tiple com­puters and can be used con­cur­rently by mul­tiple ap­plic­a­tions. A sender or pro­du­cer is a pro­gram that sends a mes­sage by writ­ing the mes­sage to a chan­nel. A re­ceiver or con­sumer is a pro­gram that re­ceives a mes­sage by read­ing (and de­let­ing) it from a chan­nel.

                          消息本身只是某种数据结构,例如字符串、字节数组、记录或对象。它可以简单地解释为数据、接收方调用的命令的描述或发送方发生的事件的描述。消息实际上包含两部分,标题和正文。标包含有关消息发送者、消息去向等的元信息;该信息由消息传递系统使用,并且大部分被使用消息的应用程序忽略。身体_包含正在传输的应用程序数据,通常会被消息传递系统忽略。在对话中,当使用消息传递的应用程序开发人员谈论消息时,她通常指的是消息正文中的数据。

                          The mes­sage itself is simply some sort of data struc­ture­such as a string, a byte array, a record, or an object. It can be in­ter­preted simply as data, as the de­scrip­tion of a com­mand to be in­voked on the re­ceiver, or as the de­scrip­tion of an event that oc­curred in the sender. A mes­sage ac­tu­ally con­tains two parts, a header and a body. The header con­tains meta-in­form­a­tion about the mes­sage­who sent it, where it's going, and so on; this in­form­a­tion is used by the mes­saging system and is mostly ig­nored by the ap­plic­a­tions using the mes­sages. The body con­tains the ap­plic­a­tion data being trans­mit­ted and is usu­ally ig­nored by the mes­saging system. In con­ver­sa­tion, when an ap­plic­a­tion de­ve­loper who is using mes­saging talks about a mes­sage, she's usu­ally re­fer­ring to the data in the body of the mes­sage.

                          异步消息架构很强大,但需要我们重新思考我们的开发方法。与其他三种集成方法相比,接触过消息传递和消息系统的开发人员相对较少。因此,应用程序开发人员一般不太熟悉该通信平台的习惯用法和特性。

                          Asyn­chron­ous mes­saging ar­chi­tec­tures are power­ful but re­quire us to re­think our de­vel­op­ment ap­proach. As com­pared to the other three in­teg­ra­tion ap­proaches, re­l­at­ively few de­ve­lopers have had ex­pos­ure to mes­saging and mes­sage sys­tems. As a result, ap­plic­a­tion de­ve­lopers in gen­eral are not as fa­mil­iar with the idioms and pe­cu­li­ar­it­ies of this com­mu­nic­a­tions plat­form.

                            什么是消息系统?

                            What Is a Messaging System?

                            消息传递功能通常由称为消息传递系统面向消息的中间件的单独软件系统提供(妈妈)。消息传递系统管理消息传递的方式与数据库系统管理数据持久性的方式相同。正如管理员必须使用应用程序数据的架构填充数据库一样,管理员必须使用定义应用程序之间通信路径的通道来配置消息传递系统。然后消息系统协调和管理消息的发送和接收。数据库系统的主要目的是确保每个数据记录都安全地保存,同样,消息传递系统的主要任务是以可靠的方式将消息从发送者的计算机移动到接收者的计算机。

                            Mes­saging cap­ab­il­it­ies are typ­ic­ally provided by a sep­ar­ate soft­ware system called a mes­saging system or mes­sage-ori­ented mid­dle­ware (MOM). A mes­saging system man­ages mes­saging the way a data­base system man­ages data per­sist­ence. Just as an ad­min­is­trator must pop­u­late the data­base with the schema for an ap­plic­a­tion's data, an ad­min­is­trator must con­fig­ure the mes­saging system with the chan­nels that define the paths of com­mu­nic­a­tion between the ap­plic­a­tions. The mes­saging system then co­ordin­ates and man­ages the send­ing and re­ceiv­ing of mes­sages. The primary pur­pose of a data­base system is to make sure each data record is safely per­sisted, and like­wise the main task of a mes­saging system is to move mes­sages from the sender's com­puter to the re­ceiver's com­puter in a re­li­able fash­ion.

                            需要消息系统将消息从一台计算机移动到另一台计算机,因为计算机和连接它们的网络本质上是不可靠的。仅仅因为一个应用程序准备好发送数据并不意味着另一应用程序准备好接收数据。即使两个应用程序均已准备就绪,网络也可能无法正常工作或无法正确传输数据。消息传递系统通过反复尝试传输消息直到成功来克服这些限制。理想情况下,消息一次就能成功传输,但情况往往并不理想。

                            A mes­saging system is needed to move mes­sages from one com­puter to an­other be­cause com­puters and the net­works that con­nect them are in­her­ently un­re­li­able. Just be­cause one ap­plic­a­tion is ready to send data does not mean that the other ap­plic­a­tion is ready to re­ceive it. Even if both ap­plic­a­tions are ready, the net­work may not be work­ing or may fail to trans­mit the data prop­erly. A mes­saging system over­comes these lim­it­a­tions by re­peatedly trying to trans­mit the mes­sage until it suc­ceeds. Under ideal cir­cum­stances, the mes­sage is trans­mit­ted suc­cess­fully on the first try, but cir­cum­stances are often not ideal.

                            本质上,消息的传输分为五个步骤:

                            In es­sence, a mes­sage is trans­mit­ted in five steps:

                            1. 创建 发送者创建消息并用数据填充它。

                            2. Create The sender cre­ates the mes­sage and pop­u­lates it with data.

                            3. 发送 发送者将消息添加到频道。

                            4. Send The sender adds the mes­sage to a chan­nel.

                            5. 传递消息传递系统将消息从发送者的计算机移动到接收者的计算机,使接收者可以使用该消息。

                            6. De­liver The mes­saging system moves the mes­sage from the sender's com­puter to the re­ceiver's com­puter, making it avail­able to the re­ceiver.

                            7. 接收接收者从通道读取消息。

                            8. Re­ceive The re­ceiver reads the mes­sage from the chan­nel.

                            9. 过程接收方从消息中提取数据。

                            10. Pro­cess The re­ceiver ex­tracts the data from the mes­sage.

                            下图说明了这五个传输步骤,每个步骤由计算机执行,以及哪些步骤涉及消息传递系统:

                            The fol­low­ing figure il­lus­trates these five trans­mis­sion steps, which com­puter per­forms each, and which steps in­volve the mes­saging system:

                            消息传输步骤

                            Mes­sage Trans­mis­sion Step-by-Step

                            图形/in01inf01.gif

                            该图还说明了两个重要的消息传递概念:

                            This figure also il­lus­trates two im­port­ant mes­saging con­cepts:

                            1. 发送后忘记 在步骤 2 中,发送应用程序将消息发送到消息通道。发送完成后,发送者可以继续其他工作,同时消息传递系统在后台传输消息。发送者可以确信接收者最终会收到消息,而不必等到这种情况发生。

                            2. Send and forget In step 2, the send­ing ap­plic­a­tion sends the mes­sage to the mes­sage chan­nel. Once that send is com­plete, the sender can go on to other work while the mes­saging system trans­mits the mes­sage in the back­ground. The sender can be con­fid­ent that the re­ceiver will even­tu­ally re­ceive the mes­sage and does not have to wait until that hap­pens.

                            3. 存储和转发 在 步骤 2 中,当发送应用程序将消息发送到消息通道时,消息传递系统将消息存储在发送者的计算机上(内存或磁盘中)。在步骤3中,消息传送系统通过将消息从发送者的计算机转发到接收者的计算机来传递消息,然后再次将消息存储在接收者的计算机上。当消息从一台计算机移动到另一台计算机时,这种存储转发过程可能会重复多次,直到到达接收者的计算机。

                            4. Store and for­ward In step 2, when the send­ing ap­plic­a­tion sends the mes­sage to the mes­sage chan­nel, the mes­saging system stores the mes­sage on the sender's com­puter, either in memory or on disk. In step 3, the mes­saging system de­liv­ers the mes­sage by for­ward­ing it from the sender's com­puter to the re­ceiver's com­puter, and then stores the mes­sage once again on the re­ceiver's com­puter. This store-and-for­ward pro­cess may be re­peated many times as the mes­sage is moved from one com­puter to an­other until it reaches the re­ceiver's com­puter.

                            创建、发送、接收和处理步骤可能看起来像是不必要的开销。为什么不简单地将数据传递给接收者呢?通过将数据包装为消息并将其存储在消息传递系统中,应用程序将传递数据的责任委托给消息传递系统。由于数据被包装为原子消息,因此可以重试传递直至成功,并且可以确保接收者可靠地接收到数据的一份副本。

                            The create, send, re­ceive, and pro­cess steps may seem like un­ne­ces­sary over­head. Why not simply de­liver the data to the re­ceiver? By wrap­ping the data as a mes­sage and stor­ing it in the mes­saging system, the ap­plic­a­tions del­eg­ate to the mes­saging system the re­spons­ib­il­ity of de­liv­er­ing the data. Be­cause the data is wrapped as an atomic mes­sage, de­liv­ery can be re­tried until it suc­ceeds, and the re­ceiver can be as­sured of re­li­ably re­ceiv­ing ex­actly one copy of the data.

                              为什么使用消息传递?

                              Why Use Messaging?

                              现在我们知道什么是消息传递,我们应该问,为什么要使用消息传递?与任何复杂的解决方案一样,没有一个简单的答案。简单的回答是,消息传递比文件传输更直接,共享,并且比远程。然而,这只是使用消息传递可以获得的优势的开始。

                              Now that we know what mes­saging is, we should ask, Why use mes­saging? As with any soph­ist­ic­ated solu­tion, there is no one simple answer. The quick answer is that mes­saging is more im­me­di­ate than File Trans­fer, better en­cap­su­lated than Shared Data­base, and more re­li­able than Remote Pro­ced­ure In­voc­a­tion. How­ever, that's just the be­gin­ning of the ad­vant­ages that can be gained using mes­saging.

                              消息传递的具体好处包括:

                              Spe­cific be­ne­fits of mes­saging in­clude:

                              • 远程通讯。消息传递使单独的应用程序能够进行通信和传输数据。驻留在同一进程中的两个对象可以简单地共享内存中的相同数据。将数据发送到另一台计算机要复杂得多,需要将数据从一台计算机复制到另一台计算机。这意味着对象必须是“可序列化的”,也就是说,它们可以转换为可以通过网络发送的简单字节流。消息传递负责此转换,因此应用程序不必担心它。

                              • Remote Com­mu­nic­a­tion. Mes­saging en­ables sep­ar­ate ap­plic­a­tions to com­mu­nic­ate and trans­fer data. Two ob­jects that reside in the same pro­cess can simply share the same data in memory. Send­ing data to an­other com­puter is a lot more com­plic­ated and re­quires data to be copied from one com­puter to an­other. This means that ob­jects have to be "seri­al­iz­able"that is, they can be con­ver­ted into a simple byte stream that can be sent across the net­work. Mes­saging takes care of this con­ver­sion so that the ap­plic­a­tions do not have to worry about it.

                              • 平台/语言集成。当通过远程通信连接多个计算机系统时,这些系统可能使用不同的语言、技术和平台,这可能是因为它们是由独立团队随着时间的推移而开发的。集成此类不同的应用程序可能需要一个中间件的中立区域来在应用程序之间进行协商,通常使用最低的共同点,例如格式模糊的平面数据文件。在这些情况下,消息传递系统可以成为应用程序之间的通用翻译器,它可以按照自己的条件与每个人的语言和平台一起工作,但允许它们通过通用的消息传递范式进行通信。这种通用连接性是消息总线的核心图案。

                              • Plat­form/Lan­guage In­teg­ra­tion. When con­nect­ing mul­tiple com­puter sys­tems via remote com­mu­nic­a­tion, these sys­tems likely use dif­fer­ent lan­guages, tech­no­lo­gies, and plat­forms, per­haps be­cause they were de­ve­loped over time by in­de­pend­ent teams. In­teg­rat­ing such di­ver­gent ap­plic­a­tions can re­quire a neut­ral zone of mid­dle­ware to ne­go­ti­ate between the ap­plic­a­tions, often using the lowest common de­nom­in­at­or­such as flat data files with ob­scure formats. In these cir­cum­stances, a mes­saging system can be a uni­ver­sal trans­lator between the ap­plic­a­tions that works with each one's lan­guage and plat­form on its own terms yet allows them to all to com­mu­nic­ate through a common mes­saging paradigm. This uni­ver­sal con­nectiv­ity is the heart of the Mes­sage Bus pat­tern.

                              • 异步通信。消息传递支持“发送后忘记”的通信方式。发送方不必等待接收方接收并处理消息;它甚至不必等待消息系统传递消息。发送方只需要等待消息发送完毕,即消息被消息系统成功存入通道即可。一旦消息被存储,发送者就可以在后台传输消息的同时自由地执行其他工作。

                              • Asyn­chron­ous com­mu­nic­a­tion. Mes­saging en­ables a send-and-forget ap­proach to com­mu­nic­a­tion. The sender does not have to wait for the re­ceiver to re­ceive and pro­cess the mes­sage; it does not even have to wait for the mes­saging system to de­liver the mes­sage. The sender only needs to wait for the mes­sage to be sent, that is, for the mes­sage to be suc­cess­fully stored in the chan­nel by the mes­saging system. Once the mes­sage is stored, the sender is free to per­form other work while the mes­sage is trans­mit­ted in the back­ground.

                              • 可变时序。对于同步通信,调用者必须等待接收者完成对调用的处理,然后调用者才能收到结果并继续。这样,呼叫者拨打电话的速度只能与接收者执行呼叫的速度一样快。异步通信允许发送者按照自己的节奏向接收者提交请求,并且接收者按照自己的不同节奏消费请求。这允许两个应用程序以最大吞吐量运行,并且不会浪费时间等待彼此(至少直到接收者用完要处理的消息为止)。

                              • Vari­able timing. With syn­chron­ous com­mu­nic­a­tion, the caller must wait for the re­ceiver to finish pro­cess­ing the call before the caller can re­ceive the result and con­tinue. In this way, the caller can make calls only as fast as the re­ceiver can per­form them. Asyn­chron­ous com­mu­nic­a­tion allows the sender to submit re­quests to the re­ceiver at its own pace and the re­ceiver to con­sume the re­quests at its own dif­fer­ent pace. This allows both ap­plic­a­tions to run at max­imum through­put and not waste time wait­ing on each other (at least until the re­ceiver runs out of mes­sages to pro­cess).

                              • 节流。远程过程调用 (RPC) 的一个问题是,单个接收器上同时调用太多远程过程调用可能会使接收器过载。这可能会导致性能下降,甚至导致接收器崩溃。由于消息传递系统会对请求进行排队,直到接收者准备好处理它们,因此接收者可以控制其消耗请求的速率,以免因太多同时请求而导致过载。调用者不受此限制的影响,因为通信是异步的,因此调用者不会因等待接收者而被阻止。

                              • Throt­tling. A prob­lem with remote pro­ced­ure calls (RPCs) is that too many of them on a single re­ceiver at the same time can over­load the re­ceiver. This can cause per­form­ance de­grad­a­tion and even cause the re­ceiver to crash. Be­cause the mes­saging system queues up re­quests until the re­ceiver is ready to pro­cess them, the re­ceiver can con­trol the rate at which it con­sumes re­quests so as not to become over­loaded by too many sim­ul­tan­eous re­quests. The callers are un­af­fected by this throt­tling be­cause the com­mu­nic­a­tion is asyn­chron­ous, so the callers are not blocked wait­ing on the re­ceiver.

                              • 可靠的通讯。消息传递提供了 RPC 无法提供的可靠传递。消息传递比 RPC 更可靠的原因是消息传递使用存储转发方法来传输消息。数据被打包为消息,消息是原子的、独立的单元。当发送者发送消息时,消息系统存储该消息。然后,它通过将消息转发到接收者的计算机来传递消息,并再次存储该消息。假定将消息存储在发送者的计算机和接收者的计算机上是可靠的。(为了使其更加可靠,消息可以存储到磁盘而不是内存;请参阅保证传递.) 不可靠的是将消息从发送者的计算机转发(移动)到接收者的计算机,因为接收者或网络可能无法正常运行。消息传递系统通过重新发送消息直到成功来克服这个问题。这种自动重试使消息传递系统能够克服网络问题,以便发送者和接收者不必担心这些细节。

                              • Re­li­able com­mu­nic­a­tion. Mes­saging provides re­li­able de­liv­ery that an RPC cannot. The reason mes­saging is more re­li­able than RPC is that mes­saging uses a store-and-for­ward ap­proach to trans­mit­ting mes­sages. The data is pack­aged as mes­sages, which are atomic, in­de­pend­ent units. When the sender sends a mes­sage, the mes­saging system stores the mes­sage. It then de­liv­ers the mes­sage by for­ward­ing it to the re­ceiver's com­puter, where it is stored again. Stor­ing the mes­sage on the sender's com­puter and the re­ceiver's com­puter is as­sumed to be re­li­able. (To make it even more re­li­able, the mes­sages can be stored to disk in­stead of memory; see Guar­an­teed De­liv­ery.) What is un­re­li­able is for­ward­ing (moving) the mes­sage from the sender's com­puter to the re­ceiver's com­puter, be­cause the re­ceiver or the net­work may not be run­ning prop­erly. The mes­saging system over­comes this by re­send­ing the mes­sage until it suc­ceeds. This auto­matic retry en­ables the mes­saging system to over­come prob­lems with the net­work so that the sender and re­ceiver don't have to worry about these de­tails.

                              • 断线操作。某些应用程序专门设计为在与网络断开连接的情况下运行,但在网络连接可用时仍与服务器同步。此类应用程序部署在笔记本电脑和 PDA 等平台上。消息传递非常适合使这些应用程序能够同步,要同步的数据可以在创建时排队,等待应用程序重新连接到网络。

                              • Dis­con­nec­ted op­er­a­tion. Some ap­plic­a­tions are spe­cific­ally de­signed to run dis­con­nec­ted from the net­work, yet to syn­chron­ize with serv­ers when a net­work con­nec­tion is avail­able. Such ap­plic­a­tions are de­ployed on plat­forms like laptop com­puters and PDAs. Mes­saging is ideal for en­abling these ap­plic­a­tions to syn­chron­izedata to be syn­chron­ized can be queued as it is cre­ated, wait­ing until the ap­plic­a­tion re­con­nects to the net­work.

                              • 调解。消息传递系统充当中介者模式[GoF ] 中所有可以发送和接收消息的程序之间的中介者。应用程序可以将其用作可集成的其他应用程序或服务的目录。如果某个应用程序与其他应用程序断开连接,它只需重新连接到消息传递系统,而不需要重新连接到所有其他消息传递应用程序。消息传递系统可以利用冗余资源来提供高可用性、平衡负载、重新路由失败的网络连接以及调整性能和服务质量。

                              • Me­di­ation. The mes­saging system acts as a me­di­at­oras in the Me­di­ator pat­tern [GoF]between all of the pro­grams that can send and re­ceive mes­sages. An ap­plic­a­tion can use it as a dir­ect­ory of other ap­plic­a­tions or ser­vices avail­able to in­teg­rate with. If an ap­plic­a­tion be­comes dis­con­nec­ted from the others, it need only re­con­nect to the mes­saging system, not to all of the other mes­saging ap­plic­a­tions. The mes­saging system can employ re­dund­ant re­sources to provide high avail­ab­il­ity, bal­ance load, reroute around failed net­work con­nec­tions, and tune per­form­ance and qual­ity of ser­vice.

                              • 线程管理。异步通信意味着一个应用程序在等待另一应用程序执行任务时不必阻塞,除非它愿意。调用者可以使用回调来在回复到达时提醒调用者,而不是阻塞等待回复。(参见请求-回复模式。)大量被阻止的线程或长时间被阻止的线程可能会使应用程序的可用线程太少而无法执行实际工作。此外,如果具有动态数量的阻塞线程的应用程序崩溃,那么当应用程序重新启动并恢复其先前状态时,重新建立这些线程将很困难。对于回调,唯一阻塞的线程是一小部分已知数量的等待回复的侦听器。这使得大多数线程可用于其他工作,并定义了已知数量的侦听器线程,这些线程可以在崩溃后轻松重新建立。

                              • Thread man­age­ment. Asyn­chron­ous com­mu­nic­a­tion means that one ap­plic­a­tion does not have to block while wait­ing for an­other ap­plic­a­tion to per­form a task, unless it wants to. Rather than block­ing to wait for a reply, the caller can use a call­back that will alert the caller when the reply ar­rives. (See the Re­quest-Reply pat­tern.) A large number of blocked threads or threads blocked for a long time can leave the ap­plic­a­tion with too few avail­able threads to per­form real work. Also, if an ap­plic­a­tion with a dy­namic number of blocked threads crashes, rees­tab­lish­ing those threads will be dif­fi­cult when the ap­plic­a­tion re­starts and re­cov­ers its former state. With call­backs, the only threads that block are a small, known number of listen­ers wait­ing for replies. This leaves most threads avail­able for other work and defines a known number of listener threads that can easily be rees­tab­lished after a crash.

                              因此,应用程序或企业可以从消息传递中受益的原因有很多。其中一些是应用程序开发人员最容易涉及的技术细节,而另一些则是最能与企业架构师产生共鸣的战略决策。这些原因中哪一个最重要取决于您特定应用程序的当前要求。它们都是使用消息传递的好理由,因此请充分利用可为您带来最大好处的理由。

                              So, there are a number of dif­fer­ent reas­ons an ap­plic­a­tion or en­ter­prise may be­ne­fit from mes­saging. Some of these are tech­nical de­tails that ap­plic­a­tion de­ve­lopers relate most read­ily to, whereas others are stra­tegic de­cisions that res­on­ate best with en­ter­prise ar­chi­tects. Which of these reas­ons is most im­port­ant de­pends on the cur­rent re­quire­ments of your par­tic­u­lar ap­plic­a­tions. They're all good reas­ons to use mes­saging, so take ad­vant­age of whichever reas­ons provide the most be­ne­fit to you.

                                异步消息传递的挑战

                                Challenges of Asynchronous Messaging

                                异步消息传递并不是集成的万能药。它解决了以优雅的方式集成不同系统的许多挑战,但它也带来了新的挑战。其中一些挑战是异步模型固有的,而其他挑战则因消息传递系统的具体实现而异。

                                Asyn­chron­ous mes­saging is not the pan­acea of in­teg­ra­tion. It re­solves many of the chal­lenges of in­teg­rat­ing dis­par­ate sys­tems in an el­eg­ant way, but it also in­tro­duces new chal­lenges. Some of these chal­lenges are in­her­ent in the asyn­chron­ous model, while other chal­lenges vary with the spe­cific im­ple­ment­a­tion of a mes­saging system.

                                • 复杂的编程模型。异步消息传递要求开发人员使用事件驱动的编程模型。应用程序逻辑不再可以在调用其他方法的单个方法中进行编码,而是现在将逻辑分为多个响应传入消息的事件处理程序。这样的系统更加复杂,更难开发和调试。例如,简单方法调用的等价物可能需要请求消息和请求通道、回复消息和回复通道、相关标识符和无效消息队列(如请求-回复中所述

                                • Com­plex pro­gram­ming model. Asyn­chron­ous mes­saging re­quires de­ve­lopers to work with an event-driven pro­gram­ming model. Ap­plic­a­tion logic can no longer be coded in a single method that in­vokes other meth­ods, but in­stead the logic is now split up into a number of event hand­lers that re­spond to in­com­ing mes­sages. Such a system is more com­plex and harder to de­velop and debug. For ex­ample, the equi­val­ent of a simple method call can re­quire a re­quest mes­sage and a re­quest chan­nel, a reply mes­sage and a reply chan­nel, a cor­rel­a­tion iden­ti­fier and an in­valid mes­sage queue (as de­scribed in Re­quest-Reply).

                                • 顺序问题。消息通道保证消息传递,但不保证消息何时传递。这可能会导致按顺序发送的消息乱序。在消息相互依赖的情况下,必须特别注意重新建立消息序列(请参阅Resequencer )

                                • Se­quence issues. Mes­sage chan­nels guar­an­tee mes­sage de­liv­ery, but they do not guar­an­tee when the mes­sage will be de­livered. This can cause mes­sages that are sent in se­quence to get out of se­quence. In situ­ations where mes­sages depend on each other, spe­cial care has to be taken to rees­tab­lish the mes­sage se­quence (see Resequen­cer).

                                • 同步场景。并非所有应用程序都可以在“发送后不管”模式下运行。如果用户正在寻找机票,他或她会希望立即查看机票价格,而不是在某个不确定的时间之后查看。因此,许多消息系统需要弥合同步和异步解决方案之间的差距。

                                • Syn­chron­ous scen­arios. Not all ap­plic­a­tions can op­er­ate in a send-and-forget mode. If a user is look­ing for air­line tick­ets, he or she is going to want to see the ticket price right away, not after some un­deter­mined time. There­fore, many mes­saging sys­tems need to bridge the gap between syn­chron­ous and asyn­chron­ous solu­tions.

                                • 性能。消息系统确实增加了一些通信开销。将应用程序数据打包成消息并发送,以及接收消息并处理它都需要花费精力。如果您必须传输大量数据,将其分成无数小块可能不是一个明智的主意。例如,如果集成解决方案需要在两个现有系统之间同步信息,第一步通常是将所有相关信息从一个系统复制到另一个系统。对于这样的批量数据复制步骤,ETL(提取、转换和加载)工具比消息传递更有效。消息传递最适合在初始数据复制后保持系统同步。

                                • Per­form­ance. Mes­saging sys­tems do add some over­head to com­mu­nic­a­tion. It takes effort to pack­age ap­plic­a­tion data into a mes­sage and send it, and to re­ceive a mes­sage and pro­cess it. If you have to trans­port a huge chunk of data, di­vid­ing it into a gazil­lion small pieces may not be a smart idea. For ex­ample, if an in­teg­ra­tion solu­tion needs to syn­chron­ize in­form­a­tion between two ex­ist­ing sys­tems, the first step is usu­ally to rep­lic­ate all rel­ev­ant in­form­a­tion from one system to the other. For such a bulk data rep­lic­a­tion step, ETL (ex­tract, trans­form, and load) tools are much more ef­fi­cient than mes­saging. Mes­saging is best suited to keep­ing the sys­tems in sync after the ini­tial data rep­lic­a­tion.

                                • 平台支持有限。许多专有消息传递系统并非在所有平台上都可用。通常,通过 FTP 传输文件是唯一的集成选项,因为目标平台可能不支持消息传递系统。

                                • Lim­ited plat­form sup­port. Many pro­pri­et­ary mes­saging sys­tems are not avail­able on all plat­forms. Often, trans­fer­ring a file via FTP is the only in­teg­ra­tion option be­cause the target plat­form may not sup­port a mes­saging system.

                                • 供应商锁定。许多消息传递系统的实现都依赖于专有协议。即使是常见的消息传递规范(例如 JMS)也无法控制解决方案的物理实现。因此,不同的消息传递系统通常不会相互连接。这可能会给您带来全新的集成挑战:集成多个集成解决方案!(请参阅消息传递桥模式。)

                                • Vendor lock-in. Many mes­saging system im­ple­ment­a­tions rely on pro­pri­et­ary pro­to­cols. Even common mes­saging spe­cific­a­tions such as JMS do not con­trol the phys­ical im­ple­ment­a­tion of the solu­tion. As a result, dif­fer­ent mes­saging sys­tems usu­ally do not con­nect to one an­other. This can leave you with a whole new in­teg­ra­tion chal­lenge: in­teg­rat­ing mul­tiple in­teg­ra­tion solu­tions! (See the Mes­saging Bridge pat­tern.)

                                总之,异步消息传递并不能解决所有问题,甚至可能会产生新的问题。在决定使用消息传递解决哪些问题时,请记住这些后果。

                                In sum­mary, asyn­chron­ous mes­saging does not solve all prob­lems, and it can even create new ones. Keep these con­se­quences in mind when de­cid­ing which prob­lems to solve using mes­saging.

                                  异步思考

                                  Thinking Asynchronously

                                  消息传递是一种异步技术,可以重试传递直至成功。相反,大多数应用程序使用同步函数调用,例如,一个过程调用子过程、一个方法调用另一个方法,或者一个过程通过 RPC(例如 CORBA 和 DCOM)远程调用另一个过程。同步调用意味着调用进程在子进程执行函数时停止。即使在 RPC 场景中,被调用的子过程在不同的进程中执行,调用者也会阻塞,直到子过程将控制(和结果)返回给调用者。相反,当使用异步消息传递时,调用者使用发送后忽略的方法,允许它在发送消息后继续执行。因此,

                                  Mes­saging is an asyn­chron­ous tech­no­logy, which en­ables de­liv­ery to be re­tried until it suc­ceeds. In con­trast, most ap­plic­a­tions use syn­chron­ous func­tion call­s­for ex­ample, a pro­ced­ure call­ing a sub­pro­ced­ure, one method call­ing an­other method, or one pro­ced­ure in­vok­ing an­other re­motely through an RPC (such as CORBA and DCOM). Syn­chron­ous calls imply that the call­ing pro­cess is halted while the sub­pro­cess is ex­ecut­ing a func­tion. Even in an RPC scen­ario, where the called sub­pro­ced­ure ex­ecutes in a dif­fer­ent pro­cess, the caller blocks until the sub­pro­ced­ure re­turns con­trol (and the res­ults) to the caller. In con­trast, when using asyn­chron­ous mes­saging, the caller uses a send-and-forget ap­proach that allows it to con­tinue to ex­ecute after it sends the mes­sage. As a result, the call­ing pro­ced­ure con­tin­ues to run while the sub­pro­ced­ure is being in­voked (see figure).

                                  同步和异步调用语义

                                  Syn­chron­ous and Asyn­chron­ous Call Se­mantics

                                  图形/in01inf02.gif

                                  异步通信有很多含义。首先,我们不再有单个执行线程。多个线程使子过程能够并发运行,这可以极大地提高性能,并有助于确保某些子进程正在取得进展,即使其他子进程可能正在等待外部结果。然而,并发线程也使调试变得更加困难。其次,结果(如果有)通过回调机制到达。这使得调用者能够执行其他任务,并在结果可用时收到通知,从而提高性能。然而,这意味着调用者即使在其他任务中间也必须能够处理结果,并且必须能够记住进行调用的上下文。第三,异步子流程可以按任何顺序执行。同样,这使得一个子过程能够取得进展,即使另一个子过程不能取得进展。但这也意味着子流程必须能够以任何顺序独立运行,并且调用者必须能够确定哪个结果来自哪个子流程并将结果组合在一起。因此,异步通信具有多个优点,但需要重新考虑过程如何使用其子过程。

                                  Asyn­chron­ous com­mu­nic­a­tion has a number of im­plic­a­tions. First, we no longer have a single thread of ex­e­cu­tion. Mul­tiple threads enable sub­pro­ced­ures to run con­cur­rently, which can greatly im­prove per­form­ance and help ensure that some sub­pro­cesses are making pro­gress even while other sub­pro­cesses may be wait­ing for ex­ternal res­ults. How­ever, con­cur­rent threads also make de­bug­ging much more dif­fi­cult. Second, res­ults (if any) arrive via a call­back mech­an­ism. This en­ables the caller to per­form other tasks and be no­ti­fied when the result is avail­able, which can im­prove per­form­ance. How­ever, this means that the caller has to be able to pro­cess the result even while it is in the middle of other tasks, and it has to be able to re­mem­ber the con­text in which the call was made. Third, asyn­chron­ous sub­pro­cesses can ex­ecute in any order. Again, this en­ables one sub­pro­ced­ure to make pro­gress even while an­other cannot. But it also means that the sub-pro­cesses must be able to run in­de­pend­ently in any order, and the caller must be able to de­term­ine which result came from which sub­pro­cess and com­bine the res­ults to­gether. As a result, asyn­chron­ous com­mu­nic­a­tion has sev­eral ad­vant­ages but re­quires re­think­ing how a pro­ced­ure uses its sub­pro­ced­ures.

                                    分布式应用程序与集成

                                    Distributed Applications versus Integration

                                    本书讲述的是企业集成如何集成独立的应用程序,以便它们可以协同工作。企业应用程序通常采用n层架构(客户端/服务器架构的更复杂版本),使其能够分布在多台计算机上。尽管这会导致不同机器上的进程相互通信,但这只是应用程序分发,而不是应用程序集成。

                                    This book is about en­ter­prise in­teg­ra­tion­how to in­teg­rate in­de­pend­ent ap­plic­a­tions so that they can work to­gether. An en­ter­prise ap­plic­a­tion often in­cor­por­ates an n-tier ar­chi­tec­ture (a more soph­ist­ic­ated ver­sion of a client/server ar­chi­tec­ture), en­abling it to be dis­trib­uted across sev­eral com­puters. Even though this res­ults in pro­cesses on dif­fer­ent ma­chines com­mu­nic­at­ing with each other, this is ap­plic­a­tion dis­tri­bu­tion, not ap­plic­a­tion in­teg­ra­tion.

                                    为什么n层架构被认为是应用程序分发而不是应用程序集成?首先,通信部分紧密耦合,它们直接相互依赖,因此一层无法在没有其他层的情况下运行。其次,层之间的通信趋于同步。第三,应用程序(n层或原子)往往具有只接受快速系统响应时间的人类用户。

                                    Why is an n-tier ar­chi­tec­ture con­sidered ap­plic­a­tion dis­tri­bu­tion and not ap­plic­a­tion in­teg­ra­tion? First, the com­mu­nic­at­ing parts are tightly coupledthey de­pend­ent dir­ectly on each other, so one tier cannot func­tion without the others. Second, com­mu­nic­a­tion between tiers tends to be syn­chron­ous. Third, an ap­plic­a­tion (n-tier or atomic) tends to have human users who will only accept rapid system re­sponse times.

                                    相比之下,集成应用程序是独立的应用程序,每个应用程序都可以自行运行,但以松散耦合的方式相互协调。这使得每个应用程序能够专注于一组全面的功能,同时将相关功能委托给其他应用程序。异步通信的集成应用程序不必等待响应;他们可以在没有响应的情况下继续进行,也可以同时执行其他任务,直到有响应为止。集成应用程序往往具有广泛的时间限制,因此它们可以处理其他任务,直到获得结果,因此比大多数实时等待结果的人类用户更有耐心。

                                    In con­trast, in­teg­rated ap­plic­a­tions are in­de­pend­ent ap­plic­a­tions that can each run by them­selves but that co­ordin­ate with each other in a loosely coupled way. This en­ables each ap­plic­a­tion to focus on one com­pre­hens­ive set of func­tion­al­ity and yet del­eg­ate to other ap­plic­a­tions for re­lated func­tion­al­ity. In­teg­rated ap­plic­a­tions com­mu­nic­at­ing asyn­chron­ously don't have to wait for a re­sponse; they can pro­ceed without a re­sponse or per­form other tasks con­cur­rently until the re­sponse is avail­able. In­teg­rated ap­plic­a­tions tend to have a broad time con­straint, such that they can work on other tasks until a result be­comes avail­able, and there­fore are more pa­tient than most human users wait­ing real-time for a result.

                                      商业消息系统

                                      Commercial Messaging Systems

                                      使用异步消息传递解决方案集成系统的明显好处为创建消息传递中间件和相关工具的软件供应商开辟了一个重要的市场。我们可以将消息厂商的产品大致分为以下四类:

                                      The ap­par­ent be­ne­fits of in­teg­rat­ing sys­tems using an asyn­chron­ous mes­saging solu­tion have opened up a sig­ni­fic­ant market for soft­ware vendors cre­at­ing mes­saging mid­dle­ware and as­so­ci­ated tools. We can roughly group the mes­saging vendors' products into the fol­low­ing four cat­egor­ies:

                                      1. 操作系统。消息传递已经成为一种普遍的需求,以至于供应商已经开始将必要的软件基础设施集成到操作系统或数据库平台中。例如,Microsoft Windows 2000 和Windows XP 操作系统包括Microsoft 消息队列(MSMQ) 服务软件。该服务可通过许多 API 访问,包括 COM 组件和 System.Messaging 命名空间(Microsoft .NET 平台的一部分)。同样,Oracle 提供 Oracle AQ 作为其数据库平台的一部分。

                                      2. Op­er­at­ing sys­tems. Mes­saging has become such a common need that vendors have star­ted to in­teg­rate the ne­ces­sary soft­ware in­fra­struc­ture into the op­er­at­ing system or data­base plat­form. For ex­ample, the Mi­crosoft Win­dows 2000 and Win­dows XP op­er­at­ing sys­tems in­clude the Mi­crosoft Mes­sage Queuing (MSMQ) ser­vice soft­ware. This ser­vice is ac­cess­ible through a number of APIs, in­clud­ing COM com­pon­ents and the System.Mes­saging namespace, part of the Mi­crosoft .NET plat­form. Sim­il­arly, Oracle offers Oracle AQ as part of its data­base plat­form.

                                      3. 应用服务器。Sun Microsystems 首先将 Java 消息服务 (JMS) 合并到 J2EE 规范 1.2 版中。从那时起,几乎所有 J2EE 应用服务器(例如 IBM WebSphere 和 BEA WebLogic)都提供了该规范的实现。此外,Sun 还通过 J2EE JDK 提供了 JMS 参考实现。

                                      4. Ap­plic­a­tion serv­ers. Sun Mi­crosys­tems first in­cor­por­ated the Java Mes­saging Ser­vice (JMS) into ver­sion 1.2 of the J2EE spe­cific­a­tion. Since then, vir­tu­ally all J2EE ap­plic­a­tion serv­ers (such as IBM Web­Sphere and BEA Web­Lo­gic) provide an im­ple­ment­a­tion for this spe­cific­a­tion. Also, Sun de­liv­ers a JMS ref­er­ence im­ple­ment­a­tion with the J2EE JDK.

                                      5. EAI 套件。这些供应商的产品提供专有但功能丰富的套件,包括消息传递、业务流程自动化、工作流程、门户和其他功能。该市场的主要参与者包括 IBM WebSphere MQ、Microsoft BizTalk、TIBCO、WebMethods、SeeBeyond、Vitria、CrossWorlds 等。其中许多产品都将 JMS 作为其支持的众多客户端 API 之一,而 SonicSoftware 和 Fiorano 等其他供应商主要专注于实现符合 JMS 的消息传递基础设施。

                                      6. EAI suites. Products from these vendors offer pro­pri­et­ary­but func­tion­ally rich­suites that en­com­pass mes­saging, busi­ness pro­cess auto­ma­tion, work­flow, portals, and other func­tions. Key play­ers in this mar­ket­place are IBM Web­Sphere MQ, Mi­crosoft BizTalk, TIBCO, Web­Meth­ods, See­Bey­ond, Vitria, Cross­Worlds, and others. Many of these products in­clude JMS as one of the many client APIs they sup­port, while other vendorssuch as Son­ic­Soft­ware and Fior­ano­fo­cus primar­ily on im­ple­ment­ing JMS-com­pli­ant mes­saging in­fra­struc­tures.

                                      7. Web 服务工具包。Web 服务引起了企业集成社区的极大兴趣。标准机构和联盟正在积极致力于标准化 Web 服务上的可靠消息传递(即 WS-Reliability、WS-ReliableMessaging 和 ebMS)。越来越多的供应商提供了实现基于 Web 服务的解决方案的路由、转换和管理的工具。

                                      8. Web ser­vices toolkits. Web ser­vices have garnered a lot of in­terest in the en­ter­prise in­teg­ra­tion com­munit­ies. Stand­ards bodies and con­sor­tia are act­ively work­ing on stand­ard­iz­ing re­li­able mes­sage de­liv­ery over Web ser­vices (i.e., WS-Re­li­ab­il­ity, WS-Re­li­ableMes­saging, and ebMS). A grow­ing number of vendors offer tools that im­ple­ment rout­ing, trans­form­a­tion, and man­age­ment of Web ser­vices-based solu­tions.

                                      本书中的模式与供应商无关,适用于大多数消息传递解决方案。不幸的是,每个供应商在描述消息传递解决方案时都倾向于定义自己的术语。在本书中,我们努力选择技术和产品中立但具有描述性且易于对话使用的模式名称。

                                      The pat­terns in this book are vendor-in­de­pend­ent and apply to most mes­saging solu­tions. Un­for­tu­nately, each vendor tends to define its own ter­min­o­logy when de­scrib­ing mes­saging solu­tions. In this book, we strove to choose pat­tern names that are tech­no­logy- and product-neut­ral yet de­script­ive and easy to use con­ver­sa­tion­ally.

                                      许多消息传递供应商已将本书的一些模式作为其产品的功能,这简化了模式的应用并加速了解决方案的开发。熟悉特定供应商术语的读者很可能会认识本书中的许多概念。为了帮助这些读者将模式语言映射到供应商特定的术语,下表将最常见的模式名称映射到一些最广泛使用的消息传递产品中相应的产品功能名称。

                                      Many mes­saging vendors have in­cor­por­ated some of this book's pat­terns as fea­tures of their products, which sim­pli­fies ap­ply­ing the pat­terns and ac­cel­er­ates solu­tion de­vel­op­ment. Read­ers who are fa­mil­iar with a par­tic­u­lar vendor's ter­min­o­logy will most likely re­cog­nize many of the con­cepts in this book. To help these read­ers map the pat­tern lan­guage to the vendor-spe­cific ter­min­o­logy, the fol­low­ing tables map the most common pat­tern names to their cor­res­pond­ing product fea­ture names in some of the most widely used mes­saging products.

                                      企业集成模式

                                      En­ter­prise In­teg­ra­tion Pat­terns

                                      Java消息服务(JMS

                                      Java Mes­sage Ser­vice (JMS)

                                      微软MSMQ

                                      Mi­crosoft MSMQ

                                      WebSphere MQ

                                      Web­Sphere MQ

                                      留言通道

                                      Mes­sage Chan­nel

                                      目的地

                                      Des­tin­a­tion

                                      消息队列

                                      Mes­sageQueue

                                      队列

                                      Queue

                                      点对点通道

                                      Point-to-Point Chan­nel

                                      队列

                                      Queue

                                      消息队列

                                      Mes­sageQueue

                                      队列

                                      Queue

                                      发布订阅通道

                                      Pub­lish-Sub­scribe Chan­nel

                                      话题

                                      Topic

                                      信息

                                      Mes­sage

                                      信息

                                      Mes­sage

                                      信息

                                      Mes­sage

                                      信息

                                      Mes­sage

                                      消息端点

                                      Mes­sage En­d­point

                                      消息生产者、消息消费者

                                      Mes­sage­Pro­du­cer, Mes­sage­Con­sumer

                                        

                                      企业集成模式

                                      En­ter­prise In­teg­ra­tion Pat­terns

                                      TIBCO

                                      TIBCO

                                      网络方法

                                      Web­Meth­ods

                                      超越

                                      See­Bey­ond

                                      维特里亚

                                      Vitria

                                      留言通道

                                      Mes­sage Chan­nel

                                      主题

                                      Sub­ject

                                      队列

                                      Queue

                                      智能排队

                                      In­tel­li­gent Queue

                                      渠道

                                      Chan­nel

                                      点对点通道

                                      Point-to-Point Chan­nel

                                      分布式队列

                                      Dis­trib­uted Queue

                                      采取行动

                                      De­liver Action

                                      智能排队

                                      In­tel­li­gent Queue

                                      渠道

                                      Chan­nel

                                      发布订阅通道

                                      Pub­lish-Sub­scribe Chan­nel

                                      主题

                                      Sub­ject

                                      发布-订阅操作

                                      Pub­lish-Sub­scribe Action

                                      智能排队

                                      In­tel­li­gent Queue

                                      发布订阅通道

                                      Pub­lish-Sub­scribe Chan­nel

                                      信息

                                      Mes­sage

                                      信息

                                      Mes­sage

                                      文档

                                      Doc­u­ment

                                      事件

                                      Event

                                      事件

                                      Event

                                      消息端点

                                      Mes­sage En­d­point

                                      发布者、订阅者

                                      Pub­lisher, Sub­scriber

                                      发布者、订阅者

                                      Pub­lisher, Sub­scriber

                                      发布者、订阅者

                                      Pub­lisher, Sub­scriber

                                      发布者、订阅者

                                      Pub­lisher, Sub­scriber

                                        图案形式

                                        Pattern Form

                                        本书包含一组组织成模式语言的模式。《设计模式》、《面向模式J2EE的模式》和《企业应用程序架构模式》书籍已经普及了使用模式来记录计算机编程技术的概念。Christopher Alexander 在他的著作《A Pattern Language》和《A Timeless Way of Building》中开创了模式和模式语言的概念。每个模式都代表一个必须做出的决定以及该决定中的考虑因素。模式语言是一个由相关模式组成的网络,其中每个模式都引向其他模式,从而指导您完成决策过程。这种方法是一种强大的技术,可以记录专家的知识,以便其他人可以轻松理解和应用。

                                        This book con­tains a set of pat­terns or­gan­ized into a pat­tern lan­guage. Books such as Design Pat­terns, Pat­tern Ori­ented Soft­ware Ar­chi­tec­ture, Core J2EE Pat­terns, and Pat­terns of En­ter­prise Ap­plic­a­tion Ar­chi­tec­ture have pop­ular­ized the concept of using pat­terns to doc­u­ment com­puter-pro­gram­ming tech­niques. Chris­topher Al­ex­an­der pi­on­eered the concept of pat­terns and pat­tern lan­guages in his books A Pat­tern Lan­guage and A Time­less Way of Build­ing. Each pat­tern rep­res­ents a de­cision that must be made and the con­sid­er­a­tions that go into that de­cision. A pat­tern lan­guage is a web of re­lated pat­terns where each pat­tern leads to others, guid­ing you through the de­cision-making pro­cess. This ap­proach is a power­ful tech­nique for doc­u­ment­ing an expert's know­ledge so that it can be read­ily un­der­stood and ap­plied by others.

                                        模式语言教您如何在有限的问题空间内解决无限种问题。由于每次要解决的总体问题都不同,因此模式的路径及其应用方式也是独特的。本书是为任何在任何应用程序中使用任何消息传递工具的人编写的,它可以专门针对您以及您所面临的独特的消息传递应用程序。

                                        A pat­tern lan­guage teaches you how to solve a lim­it­less vari­ety of prob­lems within a bounded prob­lem space. Be­cause the over­all prob­lem that is being solved is dif­fer­ent every time, the path through the pat­terns and how they're ap­plied is also unique. This book is writ­ten for anyone using any mes­saging tools for any ap­plic­a­tion, and it can be ap­plied spe­cific­ally for you and the unique ap­plic­a­tion of mes­saging that you face.

                                        单独使用模式形式并不能保证一本书包含丰富的知识。仅仅说“当你遇到这个问题时,应用这个解决方案”是不够的。为了让您真正从模式中学习,该模式必须记录为什么问题难以解决,考虑实际上效果不佳的可能解决方案,并解释为什么所提供的解决方案是最好的解决方案。同样,这些模式需要相互连接,以便引导您从一个问题到下一个问题。通过这种方式,模式形式不仅可以用来教导应用什么解决方案,还可以用来教导如何解决作者无法预测的问题。这些是我们在本书中努力实现的目标。

                                        Using the pat­tern form by itself does not guar­an­tee that a book con­tains a wealth of know­ledge. It is not enough to simply say, "When you face this prob­lem, apply this solu­tion." For you to truly learn from a pat­tern, the pat­tern has to doc­u­ment why the prob­lem is dif­fi­cult to solve, con­sider pos­sible solu­tions that in fact don't work well, and ex­plain why the solu­tion offered is the best avail­able. Like­wise, the pat­terns need to con­nect to each other so as to walk you from one prob­lem to the next. In this way, the pat­tern form can be used to teach not just what solu­tions to apply but also how to solve prob­lems the au­thors could not have pre­dicted. These are goals we strive to ac­com­plish in this book.

                                        模式应该是规范性的,这意味着它们应该告诉您要做什么。它们不只是描述问题,也不只是描述如何解决问题,而是告诉您如何解决问题。每个模式代表您必须做出的决定:“我应该使用消息传递吗? ” “命令信息对我有帮助吗?” 模式和模式语言的目的是帮助您做出决策,从而为您的特定问题提供良好的解决方案,即使作者没有考虑到该特定问题,即使您没有知识和经验自己开发该解决方案的经验。

                                        Pat­terns should be pre­script­ive, mean­ing that they should tell you what to do. They don't just de­scribe a prob­lem, and they don't just de­scribe how to solve itthey tell you what to do to solve it. Each pat­tern rep­res­ents a de­cision you must make: "Should I use Mes­saging?" "Would a Com­mand Mes­sage help me here?" The point of the pat­terns and the pat­tern lan­guage is to help you make de­cisions that lead to a good solu­tion for your spe­cific prob­lem, even if the au­thors didn't have that spe­cific prob­lem in mind and even if you don't have the know­ledge and ex­per­i­ence to de­velop that solu­tion on your own.

                                        没有一种通用的模式形式;不同的书使用不同的结构。我们使用了一种与亚历山大形式相当接近的风格,这种风格首先在Kent Beck的《Smalltalk 最佳实践模式》中在计算机编程中得到普及。我们喜欢亚历山大形式,因为它产生的模式更像散文。因此,尽管每个模式都遵循相同的、定义明确的结构,但该格式避免了各个小节的标题,这会破坏讨论的流程。为了提高导航性,该格式使用下划线、缩进和插图等样式元素来帮助您快速识别重要信息。

                                        There is no one uni­ver­sal pat­tern form; dif­fer­ent books use vari­ous struc­tures. We used a style that is fairly close to the Al­ex­an­drian form, which was first pop­ular­ized for com­puter pro­gram­ming in Small­talk Best Prac­tice Pat­terns by Kent Beck. We like the Al­ex­an­drian form be­cause it res­ults in pat­terns that are more prose-like. As a result, even though each pat­tern fol­lows an identical, well-defined struc­ture, the format avoids head­ings for in­di­vidual sub­sec­tions, which would dis­rupt the flow of the dis­cus­sion. To im­prove nav­ig­ab­il­ity, the format uses style ele­ments such as un­der­scor­ing, in­dent­a­tion, and il­lus­tra­tions to help you identify im­port­ant in­form­a­tion at a quick glance.

                                        每个模式都遵循以下结构:

                                        Each pat­tern fol­lows this struc­ture:

                                        • 名称 这是模式的标识符,指示模式的用途。我们选择了易于在句子中使用的名称,以便在设计师之间的对话中轻松引用模式的概念。

                                        • Name This is an iden­ti­fier for the pat­tern that in­dic­ates what the pat­tern does. We chose names that can easily be used in a sen­tence so that it is easy to ref­er­ence the pat­tern's concept in a con­ver­sa­tion between de­sign­ers.

                                        • 图标 除了模式名称之外,大多数模式还与图标相关联。由于许多建筑师习惯于通过图表进行视觉交流,因此除了口头语言之外,我们还提供视觉语言。这种视觉语言强调了模式的可组合性,因为可以组合多个模式图标来描述更大、更复杂模式的解决方案。

                                        • Icon Most pat­terns are as­so­ci­ated with an icon in ad­di­tion to the pat­tern name. Be­cause many ar­chi­tects are used to com­mu­nic­at­ing visu­ally through dia­grams, we provide a visual lan­guage in ad­di­tion to the verbal lan­guage. This visual lan­guage un­der­lines the com­pos­ab­il­ity of the pat­terns, as mul­tiple pat­tern icons can be com­bined to de­scribe the solu­tion of a larger, more com­plex pat­tern.

                                        • 上下文 本节解释什么类型的工作可能会让您遇到此模式解决的问题。上下文为问题奠定了基础,并且通常指的是您可能已经应用的其他模式。

                                        • Con­text This sec­tion ex­plains what type of work might make you run into the prob­lem that this pat­tern solves. The con­text sets the stage for the prob­lem and often refers to other pat­terns you may have already ap­plied.

                                        • 问题 这解释了您所面临的困难,以问题的形式表达。您应该能够阅读问题陈述并快速确定该模式是否与您的工作相关。我们已将问题格式化为由水平规则分隔的一个句子。

                                        • Prob­lem This ex­plains the dif­fi­culty you are facing, ex­pressed as a ques­tion. You should be able to read the prob­lem state­ment and quickly de­term­ine if this pat­tern is rel­ev­ant to your work. We've format­ted the prob­lem to be one sen­tence de­lim­ited by ho­ri­zontal rules.

                                        • 力量 力量探索使问题难以解决的限制因素。他们经常考虑看似有前途但效果不佳的替代解决方案,这有助于展示真正解决方案的价值。

                                        • Forces The forces ex­plore the con­straints that make the prob­lem dif­fi­cult to solve. They often con­sider al­tern­at­ive solu­tions that seem prom­ising but don't pan out, which helps show the value of the real solu­tion.

                                        • 解决方案 本部分说明您应该采取哪些措施来解决该问题。它不限于您的特定情况,而是描述了在问题所代表的各种情况下该怎么做。如果您了解模式的问题和解决方案,您就了解了该模式。我们采用与问题相同的风格来格式化解决方案,以便您在阅读本书时可以轻松发现问题和解决方案的陈述。

                                        • Solu­tion This part ex­plains what you should do to solve the prob­lem. It is not lim­ited to your par­tic­u­lar situ­ation, but de­scribes what to do in the vari­ety of cir­cum­stances rep­res­en­ted by the prob­lem. If you un­der­stand a pat­tern's prob­lem and solu­tion, you un­der­stand the pat­tern. We've format­ted the solu­tion in the same style as the prob­lem so that you can easily spot prob­lem and solu­tion state­ments when per­us­ing the book.

                                        • 草图 亚历山大形式最吸引人的特性之一是每个模式都包含一个说明解决方案的草图。很多时候,只要看图案名称和草图,就可以了解图案的本质。我们试图通过紧跟每个模式的解决方案陈述之后用图来说明解决方案来保持这种风格。

                                        • Sketch One of the most ap­peal­ing prop­er­ties of the Al­ex­an­drian form is that each pat­tern con­tains a sketch that il­lus­trates the solu­tion. In many cases, just by look­ing at the pat­tern name and the sketch, you can un­der­stand the es­sence of the pat­tern. We tried to main­tain this style by il­lus­trat­ing the solu­tion with a figure im­me­di­ately fol­low­ing the solu­tion state­ment of each pat­tern.

                                        • 结果 这部分对解决方案进行了扩展,以解释如何应用该解决方案以及如何解决力的详细信息。它还解决了应用此模式可能出现的新挑战。

                                        • Res­ults This part ex­pands upon the solu­tion to ex­plain the de­tails of how to apply the solu­tion and how it re­solves the forces. It also ad­dresses new chal­lenges that may arise as a result of ap­ply­ing this pat­tern.

                                        • 下一步 本节列出了应用当前模式后要考虑的其他模式。模式并不是孤立存在的;一种模式的应用通常会导致您遇到其他模式可以解决的新问题。模式之间的关系构成了模式语言,而不仅仅是模式目录。

                                        • Next This sec­tion lists other pat­terns to be con­sidered after ap­ply­ing the cur­rent one. Pat­terns don't live in isol­a­tion; the ap­plic­a­tion of one pat­tern usu­ally leads you to new prob­lems that are solved by other pat­terns. The re­la­tion­ships between pat­terns are what con­sti­tutes a pat­tern lan­guage as op­posed to just a pat­tern cata­log.

                                        • 侧边栏 这些部分讨论更详细的技术问题或模式的变体。我们将这些部分在视觉上与文本的其余部分分开,因此如果它们与您的模式的特定应用不相关,您可以轻松地跳过它们。

                                        • Side­bars These sec­tions dis­cuss more de­tailed tech­nical issues or vari­ations of the pat­tern. We set these sec­tions visu­ally apart from the re­mainder of the text so you can easily skip them if they are not rel­ev­ant to your par­tic­u­lar ap­plic­a­tion of the pat­tern.

                                        • 示例 图案通常包括正在应用或已经应用的图案的一个或多个示例。示例可以像命名已知用途一样简单,也可以像一大段示例代码一样详细。鉴于可用的消息传递技术有大量,我们不希望您熟悉用于实现示例的每种技术。因此,我们设计了这些模式,以便您可以安全地跳过该示例,而不会丢失该模式的任何关键内容。

                                        • Ex­amples A pat­tern usu­ally in­cludes one or more ex­amples of the pat­tern being ap­plied or having been ap­plied. An ex­ample may be as simple as naming a known use or as de­tailed as a large seg­ment of sample code. Given the large number of avail­able mes­saging tech­no­lo­gies, we do not expect you to be fa­mil­iar with each tech­no­logy used to im­ple­ment an ex­ample. There­fore, we de­signed the pat­terns so that you can safely skip the ex­ample without losing any crit­ical con­tent of the pat­tern.

                                        将解决方案描述为模式的美妙之处在于,它不仅教您如何解决所讨论的具体问题,还教您如何创建解决作者甚至没有意识到的问题的设计。因此,这些消息传递模式不仅描述了当今存在的消息传递系统,而且还可能适用于本书出版后创建的新系统。

                                        The beauty in de­scrib­ing solu­tions as pat­terns is that it teaches you not only how to solve the spe­cific prob­lems dis­cussed, but also how to create designs that solve prob­lems the au­thors were not even aware of. As a result, these pat­terns for mes­saging not only de­scribe mes­saging sys­tems that exist today, but may also apply to new ones cre­ated well after this book is pub­lished.

                                          图表符号

                                          Diagram Notation

                                          集成解决方案由许多不同的部分组成:应用程序、数据库、端点、通道、消息、路由器等。如果我们想要描述一个集成解决方案,我们需要定义一个能够容纳所有这些不同组件的符号。据我们所知,还没有一种广泛使用的、全面的符号可以用来描述集成解决方案的各个方面。统一建模语言 (UML) 在使用类图和交互图描述面向对象的系统方面做得很好,但它不包含描述消息传递解决方案的语义。EAI 的 UML 概要文件 [ UMLEAI] 丰富了协作图的语义来描述组件之间的消息流。这种表示法作为精确的可视化规范非常有用,可以作为模型驱动架构 (MDA) 一部分的代码生成的基础。我们决定不采用这种表示法有两个原因。首先,UML Profile 并没有捕获我们的模式语言中描述的所有模式。其次,我们并不是要创建精确的视觉规范,而是要创建具有一定“草图”质量的图像。我们想要的图片能够一目了然地传达图案的本质,就像亚历山大的草图一样。这就是为什么我们决定创建我们自己的“符号”。幸运的是,与更正式的符号不同,我们的符号不需要您阅读大量手册。一张简单的图片就足够了:

                                          In­teg­ra­tion solu­tions con­sist of many dif­fer­ent pieces­ap­plic­a­tions, data­bases, en­d­points, chan­nels, mes­sages, routers, and so on. If we want to de­scribe an in­teg­ra­tion solu­tion, we need to define a nota­tion that ac­com­mod­ates all these dif­fer­ent com­pon­ents. To our know­ledge, there is no widely used, com­pre­hens­ive nota­tion that is geared toward the de­scrip­tion of all as­pects of an in­teg­ra­tion solu­tion. The Uni­fied Mod­el­ing Lan­guage (UML) does a fine job of de­scrib­ing object-ori­ented sys­tems with class and in­ter­ac­tion dia­grams, but it does not con­tain se­mantics to de­scribe mes­saging solu­tions. The UML Pro­file for EAI [UMLEAI] en­riches the se­mantics of col­lab­or­a­tion dia­grams to de­scribe mes­sage flows between com­pon­ents. This nota­tion is very useful as a pre­cise visual spe­cific­a­tion that can serve as the basis for code gen­er­a­tion as part of a model-driven ar­chi­tec­ture (MDA). We de­cided not to adopt this nota­tion for two reas­ons. First, the UML Pro­file does not cap­ture all the pat­terns de­scribed in our pat­tern lan­guage. Second, we were not look­ing to create a pre­cise visual spe­cific­a­tion, but images that have a cer­tain "sketch" qual­ity to them. We wanted pic­tures that are able to convey the es­sence of a pat­tern at a quick glancevery much like Al­ex­an­der's sketch. That's why we de­cided to create our own "nota­tion." Luck­ily, unlike the more formal nota­tion, ours does not re­quire you to read a large manual. A simple pic­ture should suf­fice:

                                          消息传递解决方案的视觉符号

                                          Visual Nota­tion for Mes­saging Solu­tions

                                          图形/in01inf03.gif

                                          这个简单的图片显示了通过通道发送到组件的消息。我们使用“组件”这个词这里它可以非常宽松地指示正在集成的应用程序、在应用程序之间转换或路由消息的中介或应用程序的特定部分。有时,如果我们想突出通道本身,我们也会将通道描绘为三维管道。通常,我们对组件更感兴趣,并将通道绘制为带箭头的简单线条。这两种表示法是等效的。我们将消息描述为一棵小树,具有圆形根和嵌套的方形元素,因为许多消息传递系统允许消息包含树状数据结构(例如 XML 文档)。树元素可以加阴影或着色以突出它们在特定模式中的用途。

                                          This simple pic­ture shows a mes­sage being sent to a com­pon­ent over a chan­nel. We use the word com­pon­ent very loosely hereit can in­dic­ate an ap­plic­a­tion that is being in­teg­rated, an in­ter­me­di­ary that trans­forms or routes the mes­sage between ap­plic­a­tions, or a spe­cific part of an ap­plic­a­tion. Some­times, we also depict a chan­nel as a three-di­men­sional pipe if we want to high­light the chan­nel itself. Often, we are more in­ter­ested in the com­pon­ents and draw the chan­nels as simple lines with arrow heads. The two nota­tions are equi­val­ent. We depict the mes­sage as a small tree with a round root and nested, square ele­ments be­cause many mes­saging sys­tems allow mes­sages to con­tain tree-like data struc­tures­for ex­ample, XML doc­u­ments. The tree ele­ments can be shaded or colored to high­light their usage in a par­tic­u­lar pat­tern. De­pict­ing mes­sages in this way allows us to provide a quick visual de­scrip­tion of trans­form­a­tion pat­ternsit is easy to show a pat­tern that adds, re­ar­ranges, or re­moves fields from the mes­sage.

                                          当我们描述应用程序设计时,例如,消息传递端点或用 C# 或 Java 编写的示例,我们确实使用标准 UML 类和序列图来描述类层次结构和对象之间的交互,因为 UML 表示法被广泛接受为描述这些类型的标准方式。解决方案(如果您需要复习 UML,请查看 [ UML ])。

                                          When we de­scribe ap­plic­a­tion designs­for ex­ample, mes­saging en­d­points or ex­amples writ­ten in C# or Javawe do use stand­ard UML class and se­quence dia­grams to depict the class hier­archy and the in­ter­ac­tion between ob­jects be­cause the UML nota­tion is widely ac­cep­ted as the stand­ard way of de­scrib­ing these types of solu­tions (if you need a re­fresher on UML, have a look at [UML]).

                                            示例和插曲

                                            Examples and Interludes

                                            我们试图通过包含使用各种集成技术的实现示例来强调这些模式的广泛适用性。这种方法的潜在缺点是您可能不熟悉示例中使用的每种技术。这就是为什么我们确保阅读示例是严格可选的,所有相关点都在模式描述中讨论。因此,您可以安全地跳过这些示例,而不会丢失重要细节。此外,在可能的情况下,我们提供了多个使用不同技术的实现示例。

                                            We have tried to un­der­line the broad ap­plic­ab­il­ity of the pat­terns by in­clud­ing im­ple­ment­a­tion ex­amples using a vari­ety of in­teg­ra­tion tech­no­lo­gies. The po­ten­tial down­side of this ap­proach is that you may not be fa­mil­iar with each tech­no­logy that is being used in an ex­ample. That's why we made sure that read­ing the ex­amples is strictly op­tion­alall rel­ev­ant points are dis­cussed in the pat­tern de­scrip­tion. There­fore, you can safely skip the ex­amples without risk of losing out on im­port­ant detail. Also, where pos­sible, we provided more than one im­ple­ment­a­tion ex­ample using dif­fer­ent tech­no­lo­gies.

                                            在提供示例代码时,我们关注的是可读性而不是可运行性。代码段可以帮助消除解决方案描述留下的任何潜在歧义,许多应用程序开发人员和架构师更喜欢查看 30 行代码而不是阅读许多文本段落。为了支持这一意图,我们通常只显示潜在更大解决方案中最相关的方法或类。我们还省略了大多数形式的错误检查,以突出代码实现的核心功能。大多数代码片段不包含内嵌注释,因为代码在代码段之前和之后的段落中进行了解释。

                                            When present­ing ex­ample code, we fo­cused on read­ab­il­ity over run­nab­il­ity. A code seg­ment can help remove any po­ten­tial am­bi­gu­ity left by the solu­tion de­scrip­tion, and many ap­plic­a­tion de­ve­lopers and ar­chi­tects prefer look­ing at 30 lines of code to read­ing many para­graphs of text. To sup­port this intent, we often show only the most rel­ev­ant meth­ods or classes of a po­ten­tially larger solu­tion. We also omit­ted most forms of error check­ing to high­light the core func­tion im­ple­men­ted by the code. Most code snip­pets do not con­tain in-line com­ments, as the code is ex­plained in the para­graphs before and after the code seg­ment.

                                            为单一集成模式提供有意义的示例具有挑战性。企业集成解决方案通常由分布在多个系统中的许多异构组件组成。同样,大多数集成模式并不是孤立运行的,而是依赖其他模式来形成有意义的解决方案。为了强调多种模式之间的协作,我们提供了更全面的示例作为插曲(请参阅第 6 章第 9 章和12 章)。这些解决方案说明了设计更全面的消息传递解决方案时涉及的许多权衡。

                                            Provid­ing a mean­ing­ful ex­ample for a single in­teg­ra­tion pat­tern is chal­len­ging. En­ter­prise in­teg­ra­tion solu­tions typ­ic­ally con­sist of a number of het­ero­gen­eous com­pon­ents spread across mul­tiple sys­tems. Like­wise, most in­teg­ra­tion pat­terns do not op­er­ate in isol­a­tion but rely on other pat­terns to form a mean­ing­ful solu­tion. To high­light the col­lab­or­a­tion between mul­tiple pat­terns, we in­cluded more com­pre­hens­ive ex­amples as in­ter­ludes (see Chapters 6, 9, and 12). These solu­tions il­lus­trate many of the trade-offs in­volved in design­ing a more com­pre­hens­ive mes­saging solu­tion.

                                            所有代码示例仅应视为说明性工具,而不应作为开发生产质量集成解决方案的起点。例如,几乎所有示例都缺乏任何形式的错误检查或对鲁棒性、安全性或可扩展性的关注。

                                            All code samples should be treated as il­lus­trat­ive tools only and not as a start­ing point for de­vel­op­ment of a pro­duc­tion-qual­ity in­teg­ra­tion solu­tion. For ex­ample, almost all ex­amples lack any form of error check­ing or con­cern for ro­bust­ness, se­cur­ity, or scalab­il­ity.

                                            我们尽可能地将示例基于免费或试用版的软件平台。在某些情况下,我们使用商业平台(例如 TIBCO ActiveEnterprise 和 Microsoft BizTalk)来说明从头开始开发解决方案与使用商业工具之间的区别。我们以这样的方式呈现这些示例,即使您无法访问所需的运行时平台,它们也具有教育意义。对于许多示例,我们使用相对简单的消息传递框架,例如 JMS 或 MSMQ。这使我们能够在示例中更加明确,并专注于手头的问题,而不是使用更复杂的中间件工具集可能提供的所有功能来分散注意力。

                                            We tried as much as pos­sible to base the ex­amples on soft­ware plat­forms that are avail­able free of charge or as a trial ver­sion. In some cases, we used com­mer­cial plat­forms (such as TIBCO Act­iveEn­ter­prise and Mi­crosoft BizTalk) to il­lus­trate the dif­fer­ence between de­vel­op­ing a solu­tion from scratch and using a com­mer­cial tool. We presen­ted those ex­amples in such a way that they are edu­ca­tional even if you do not have access to the re­quired runtime plat­form. For many ex­amples, we use re­l­at­ively bare­bones mes­saging frame­works such as JMS or MSMQ. This allows us to be more ex­pli­cit in the ex­ample and focus on the prob­lem at hand in­stead of dis­tract­ing from it with all the fea­tures a more com­plex mid­dle­ware tool­set may provide.

                                            本书中的Java 示例基于JMS 1.1 规范,该规范是J2EE 1.4 规范的一部分。到本书出版时,大多数消息传递和应用程序服务器供应商都将支持 JMS 1.1。您可以从 Sun 的网站下载 Sun Microsystems 的 JMS 规范参考实现: http: //java.sun.com/j2ee

                                            The Java ex­amples in this book are based on the JMS 1.1 spe­cific­a­tion, which is part of the J2EE 1.4 spe­cific­a­tion. By the time this book is pub­lished, most mes­saging and ap­plic­a­tion server vendors will sup­port JMS 1.1. You can down­load Sun Mi­crosys­tems' ref­er­ence im­ple­ment­a­tion of the JMS spe­cific­a­tion from Sun's Web site: http://java.sun.com/j2ee.

                                            Microsoft .NET 示例基于 .NET Framework 1.1 版,并用 C# 编写。您可以从 Microsoft 网站下载 .NET Framework SDK:http://msdn.microsoft.com/net

                                            The Mi­crosoft .NET ex­amples are based on Ver­sion 1.1 of the .NET Frame­work and are writ­ten in C#. You can down­load the .NET Frame­work SDK from Mi­crosoft's Web site: http://msdn.mi­crosoft.com/net.

                                              本书的组织结构

                                              Organization of This Book

                                              本书中的模式语言与任何模式语言一样,是一个相互引用的模式网络。同时,某些模式比其他模式更基本,形成大概念模式的层次结构,从而导致更精细的模式。大概念模式构成了模式语言的承载成员。它们是主要的模式,是提供语言基础并支持其他模式的根模式。

                                              The pat­tern lan­guage in this book, as with any pat­tern lan­guage, is a web of pat­terns re­fer­ring to each other. At the same time, some pat­terns are more fun­da­mental than others, form­ing a hier­archy of big-concept pat­terns that lead to more finely de­tailed pat­terns. The big-concept pat­terns form the load-bear­ing mem­bers of the pat­tern lan­guage. They are the main ones, the root pat­terns that provide the found­a­tion of the lan­guage and sup­port the other pat­terns.

                                              本书按抽象级别和主题领域将模式分为几章。下图显示了根模式及其与本书各章节的关系。

                                              This book groups pat­terns into chapters by level of ab­strac­tion and by topic area. The fol­low­ing dia­gram shows the root pat­terns and their re­la­tion­ship to the chapters of the book.

                                              根模式和章节的关系

                                              Re­la­tion­ship of Root Pat­terns and Chapters

                                              图形/in01inf04.gif

                                              最基本的模式是消息传递;这就是本书的主题。它导致了第 3 章“消息系统”中描述的六种根模式,即消息通道、消息管道和过滤器、消息路由器、消息转换器和消息端点。反过来,每个根模式都会导致本书中自己的章节(管道和过滤器除外,它们不是特定于消息传递的,而是一种广泛使用的架构风格,构成了路由和转换模式的基础)。

                                              The most fun­da­mental pat­tern is Mes­saging; that's what this book is about. It leads to the six root pat­terns de­scribed in Chapter 3, "Mes­saging Sys­tems," namely, Mes­sage Chan­nel, Mes­sage, Pipes and Fil­ters, Mes­sage Router, Mes­sage Trans­lator, and Mes­sage En­d­point. In turn, each root pat­tern leads to its own chapter in the book (except Pipes and Fil­ters, which is not spe­cific to mes­saging but is a widely used ar­chi­tec­tural style that forms the basis of the rout­ing and trans­form­a­tion pat­terns).

                                              模式语言分为八章,遵循刚刚描述的层次结构:

                                              The pat­tern lan­guage is di­vided into eight chapters, which follow the hier­archy just de­scribed:

                                              第 2 章“集成样式” 本章回顾了可用于集成应用程序的不同方法,包括消息传递

                                              第 3 章“消息传递系统” 本章回顾了六种根消息传递模式,概述了整个模式语言。

                                              第 4 章“消息传递通道” 应用程序通过通道进行通信。通道定义消息可以遵循的逻辑路径。本章介绍如何确定您的应用程序需要哪些通道。

                                              第 5 章“消息构造” 一旦有了消息通道,您就需要在它们上发送消息。本章解释了使用消息的不同方式以及如何利用它们的特殊属性。

                                              第 7 章“消息路由” 消息传递解决方案旨在解耦信息的发送者和接收者。消息路由器提供发送者和接收者之间的位置独立性,以便发送者不必知道谁处理他们的消息。相反,它们将消息发送到中间消息路由组件,中间消息路由组件将消息转发到正确的目的地。本章介绍了各种不同的路由技术。

                                              第 8 章“消息转换” 独立开发的应用程序通常在消息格式、所谓唯一标识符的形式和含义、甚至要使用的字符编码方面不一致。因此,需要中间组件将消息从一个应用程序生成的格式转换为接收应用程序的格式。本章介绍如何设计变压器组件。

                                              第 10 章“消息传送端点” 许多应用程序并不是为参与消息传送解决方案而设计的。因此,它们必须显式连接到消息传递系统。本章描述应用程序中负责发送和接收消息的层,使您的应用程序成为消息的端点。

                                              第 11 章“系统管理” 一旦消息系统准备好集成应用程序,我们如何确保它正确运行并执行我们想要的操作?本章探讨如何测试和监视正在运行的消息系统。

                                              Chapter 2, "In­teg­ra­tion Styles" This chapter re­views the dif­fer­ent ap­proaches avail­able for in­teg­rat­ing ap­plic­a­tions, in­clud­ing Mes­saging.

                                              Chapter 3, "Mes­saging Sys­tems" This chapter re­views the six root mes­saging pat­terns, giving an over­view of the entire pat­tern lan­guage.

                                              Chapter 4, "Mes­saging Chan­nels" Ap­plic­a­tions com­mu­nic­ate via chan­nels. Chan­nels define the lo­gical path­ways a mes­sage can follow. This chapter shows how to de­term­ine what chan­nels your ap­plic­a­tions need.

                                              Chapter 5, "Mes­sage Con­struc­tion" Once you have mes­sage chan­nels, you need mes­sages to send on them. This chapter ex­plains the dif­fer­ent ways mes­sages can be used and how to take ad­vant­age of their spe­cial prop­er­ties.

                                              Chapter 7, "Mes­sage Rout­ing" Mes­saging solu­tions aim to de­couple the sender and the re­ceiver of in­form­a­tion. Mes­sage routers provide loc­a­tion in­de­pend­ence between sender and re­ceiver so that senders don't have to know about who pro­cesses their mes­sages. Rather, they send the mes­sages to in­ter­me­di­ate mes­sage rout­ing com­pon­ents that for­ward the mes­sage to the cor­rect des­tin­a­tion. This chapter presents a vari­ety of dif­fer­ent rout­ing tech­niques.

                                              Chapter 8, "Mes­sage Trans­form­a­tion" In­de­pend­ently de­ve­loped ap­plic­a­tions often don't agree on mes­sages' formats, on the form and mean­ing of sup­posedly unique iden­ti­fi­ers, or even on the char­ac­ter en­cod­ing to be used. There­fore, in­ter­me­di­ate com­pon­ents are needed to con­vert mes­sages from the format one ap­plic­a­tion pro­duces to that of the re­ceiv­ing ap­plic­a­tions. This chapter shows how to design trans­former com­pon­ents.

                                              Chapter 10, "Mes­saging En­d­points" Many ap­plic­a­tions were not de­signed to par­ti­cip­ate in a mes­saging solu­tion. As a result, they must be ex­pli­citly con­nec­ted to the mes­saging system. This chapter de­scribes a layer in the ap­plic­a­tion that is re­spons­ible for send­ing and re­ceiv­ing the mes­sages, making your ap­plic­a­tion an en­d­point for mes­sages.

                                              Chapter 11, "System Man­age­ment" Once a mes­saging system is in place to in­teg­rate ap­plic­a­tions, how do we make sure that it's run­ning cor­rectly and doing what we want? This chapter ex­plores how to test and mon­itor a run­ning mes­saging system.

                                              这八章共同教您有关使用消息传递连接应用程序所需了解的知识。

                                              These eight chapters to­gether teach you what you need to know about con­nect­ing ap­plic­a­tions using mes­saging.

                                                入门

                                                Getting Started

                                                对于任何一本有很多东西要教的书,对于作者和读者来说都很难知道从哪里开始。直接阅读所有页面可以确保涵盖整个主题领域,但这并不是解决最有帮助的问题的最快方法。从语言中间的模式开始就像开始看一部看了一半的电影一样,你看到正在发生的事情,但不明白它的含义。

                                                With any book that has a lot to teach, it's hard to know where to start, both for the au­thors and the read­ers. Read­ing all of the pages straight through as­sures cov­er­ing the entire sub­ject area but isn't the quick­est way to get to the issues that are of the most help. Start­ing with a pat­tern in the middle of the lan­guage can be like start­ing to watch a movie that's half overyou see what's hap­pen­ing but don't un­der­stand what it means.

                                                幸运的是,模式语言是围绕前面描述的根模式形成的。这些根模式共同提供了模式语言的概述,并单独提供了深入研究消息传递细节的起点。要在不回顾所有模式的情况下对语言进行全面调查,请从回顾第 3 章中的根模式开始。

                                                Luck­ily, the pat­tern lan­guage is formed around the root pat­terns de­scribed earlier. These root pat­terns col­lect­ively provide an over­view of the pat­tern lan­guage, and in­di­vidu­ally provide start­ing points for delving deep into the de­tails of mes­saging. To get an over­all survey of the lan­guage without re­view­ing all of the pat­terns, start with re­view­ing the root pat­terns in Chapter 3.

                                                第 2 章“集成风格”概述了四种主要的应用程序集成技术,并确定消息传递是许多集成机会的最佳整体方法。如果您不熟悉应用程序集成中涉及的问题以及各种可用方法的优缺点,请阅读本章。如果您已经确信消息传递是正确的选择,并且想要开始了解如何使用消息传递,则可以完全跳过本章。

                                                Chapter 2, "In­teg­ra­tion Styles," provides an over­view of the four main ap­plic­a­tion in­teg­ra­tion tech­niques and settles on Mes­saging as being the best over­all ap­proach for many in­teg­ra­tion op­por­tun­it­ies. Read this chapter if you are un­fa­mil­iar with issues in­volved in ap­plic­a­tion in­teg­ra­tion and the pros and cons of the vari­ous ap­proaches that are avail­able. If you're already con­vinced that mes­saging is the way to go and want to get star­ted with how to use mes­saging, you can skip this chapter com­pletely.

                                                第 3 章“消息传递系统”包含该模式语言的所有根模式(除了2 章中的消息传递) 。要了解模式语言的概述,请阅读(或至少浏览)本章中的所有模式。要深入研究特定主题,请阅读其根模式,然后转到模式部分末尾提到的模式;接下来的模式都将位于以根模式命名的章节中。

                                                Chapter 3, "Mes­saging Sys­tems," con­tains all of this pat­tern lan­guage's root pat­terns (except Mes­saging, which is in Chapter 2). For an over­view of the pat­tern lan­guage, read (or at least skim) all of the pat­terns in this chapter. To dive deeply on a par­tic­u­lar topic, read its root pat­tern, then go to the pat­terns men­tioned at the end of the pat­tern sec­tion; those next pat­terns will all be in a chapter named after the root pat­tern.

                                                在第 2 章和第 3 章之后不同类型的消息传递开发人员可能对不同的章节最感兴趣,具体取决于每个组如何使用消息传递执行集成的具体情况:

                                                After Chapters 2 and 3, dif­fer­ent types of mes­saging de­ve­lopers may be most in­ter­ested in dif­fer­ent chapters based on the spe­cif­ics of how each group uses mes­saging to per­form in­teg­ra­tion:

                                                • 系统管理员可能最感兴趣的是第4 章“消息传递通道”(有关创建哪些通道的指南)和第11 章“系统管理”(有关如何维护正在运行的消息传递系统的指南)。

                                                • System ad­min­is­trat­ors may be most in­ter­ested in Chapter 4, "Mes­saging Chan­nels," the guidelines for what chan­nels to create, and Chapter 11, "System Man­age­ment," guid­ance on how to main­tain a run­ning mes­saging system.

                                                • 应用程序开发人员应该查看第 10 章“消息传送端点”,了解如何将应用程序与消息传送系统集成,并查看5 章“消息构造”,了解何时发送哪些消息。

                                                • Ap­plic­a­tion de­ve­lopers should look at Chapter 10, "Mes­saging En­d­points," to learn how to in­teg­rate an ap­plic­a­tion with a mes­saging system and at Chapter 5, "Mes­sage Con­struc­tion," to learn what mes­sages to send when.

                                                • 系统集成商将从第 7 章“消息路由”如何将消息定向到正确的接收者和第 8 章“消息转换”如何将消息从发送者的格式转换为接收者的格式中获益匪浅。

                                                • System in­teg­rat­ors will gain the most from Chapter 7, "Mes­sage Rout­ing"how to direct mes­sages to the proper re­ceiv­ersand Chapter 8, "Mes­sage Trans­form­a­tion"how to con­vert mes­sages from the sender's format to the re­ceiver's.

                                                请记住,在阅读模式时,如果您很着急,请先阅读问题和解决方案。这将为您提供足够的信息来确定您现在是否对该模式感兴趣以及您是否已经了解该模式。如果您不知道该模式并且听起来很有趣,请继续阅读其他部分。

                                                Keep in mind that when read­ing a pat­tern, if you're in a hurry, start by just read­ing the prob­lem and solu­tion. This will give you enough in­form­a­tion to de­term­ine if the pat­tern is of in­terest to you right now and if you already know the pat­tern. If you do not know the pat­tern and it sounds in­ter­est­ing, go ahead and read the other parts.

                                                还要记住,这是一种模式语言,因此模式不一定要按照书中呈现的顺序来阅读。本书的顺序通过依次考虑所有相关主题并一起讨论相关问题来教您有关消息传递的知识。要使用模式来解决特定问题,请从适当的根模式开始。它的上下文解释了在这一模式之前需要应用哪些模式,即使它们不是本书中紧邻这一模式之前的模式。同样,下一节(模式的最后一段)描述了在本模式之后考虑应用哪些模式,即使它们不是书中紧随本模式之后的模式。使用相互关联的模式网络,而不是线性的书页列表,

                                                Also re­mem­ber that this is a pat­tern lan­guage, so the pat­terns are not ne­ces­sar­ily meant to be read in the order they're presen­ted in the book. The book's order teaches you about mes­saging by con­sid­er­ing all of the rel­ev­ant topics in turn and dis­cuss­ing re­lated issues to­gether. To use the pat­terns to solve a par­tic­u­lar prob­lem, start with an ap­pro­pri­ate root pat­tern. Its con­text ex­plains what pat­terns need to be ap­plied before this one, even if they're not the ones im­me­di­ately pre­ced­ing this one in the book. Like­wise, the next sec­tion (the last para­graph of the pat­tern) de­scribes what pat­terns to con­sider ap­ply­ing after this one, even if they're not the ones im­me­di­ately fol­low­ing this one in the book. Use the web of in­ter­con­nec­ted pat­terns, not the linear list of book pages, to guide you through the ma­ter­ial.

                                                  支持网站

                                                  Supporting Web Site

                                                  请在我们的网站上查找本书的配套信息以及有关企业集成的相关信息:www.enterpriseintegrationpatterns.com。您还可以通过电子邮件将您的意见、建议和反馈发送给我们:authors@enterpriseintegrationpatterns.com

                                                  Please look for com­pan­ion in­form­a­tion to this book plus re­lated in­form­a­tion on en­ter­prise in­teg­ra­tion at our Web site: www.en­ter­pri­sein­teg­ra­tion­pat­terns.com. You can also e-mail your com­ments, sug­ges­tions, and feed­back to us at au­thors@en­ter­pri­sein­teg­ra­tion­pat­terns.com.

                                                    概括

                                                    Summary

                                                    您现在应该很好地理解了以下概念,它们是本书材料的基础:

                                                    You should now have a good un­der­stand­ing of the fol­low­ing con­cepts, which are fun­da­mental to the ma­ter­ial in this book:

                                                    • 什么是消息传递。

                                                    • What mes­saging is.

                                                    • 什么是消息系统。

                                                    • What a mes­saging system is.

                                                    • 为什么要使用消息传递。

                                                    • Why to use mes­saging.

                                                    • 异步编程与同步编程有何不同。

                                                    • How asyn­chron­ous pro­gram­ming is dif­fer­ent from syn­chron­ous pro­gram­ming.

                                                    • 应用程序集成与应用程序分发有何不同。

                                                    • How ap­plic­a­tion in­teg­ra­tion is dif­fer­ent from ap­plic­a­tion dis­tri­bu­tion.

                                                    • 哪些类型的商业产品包含消息传递系统。

                                                    • What types of com­mer­cial products con­tain mes­saging sys­tems.

                                                    您还应该了解本书将如何教您使用消息传递:

                                                    You should also have a feel for how this book is going to teach you to use mes­saging:

                                                    • 模式在构建材料中的作用。

                                                    • The role pat­terns have in struc­tur­ing the ma­ter­ial.

                                                    • 图表中使用的自定义符号的含义。

                                                    • The mean­ing of the custom nota­tion used in the dia­grams.

                                                    • 示例的目的和范围。

                                                    • The pur­pose and scope of the ex­amples.

                                                    • 材料的组织。

                                                    • The or­gan­iz­a­tion of the ma­ter­ial.

                                                    • 如何开始学习该材料。

                                                    • How to get star­ted learn­ing the ma­ter­ial.

                                                    现在您已经了解了基本概念以及材料的呈现方式,我们邀请您开始学习使用消息传递的企业集成。

                                                    Now that you un­der­stand the basic con­cepts and how the ma­ter­ial will be presen­ted, we invite you to start learn­ing about en­ter­prise in­teg­ra­tion using mes­saging.

                                                      第 1 章使用模式解决集成问题

                                                      Chapter 1. Solving Integration Problems Using Patterns

                                                      本章说明了如何使用本书中的模式来解决各种集成问题。为此,我们研究了常见的集成场景并提供了一个全面的集成示例。当我们设计此示例的解决方案时,我们使用本书中包含的模式来表达该解决方案。在本章结束时,您将熟悉大约两打集成模式。

                                                      This chapter il­lus­trates how the pat­terns in this book can be used to solve a vari­ety of in­teg­ra­tion prob­lems. In order to do so, we ex­am­ine common in­teg­ra­tion scen­arios and present a com­pre­hens­ive in­teg­ra­tion ex­ample. As we design the solu­tion to this ex­ample, we ex­press the solu­tion using the pat­terns con­tained in this book. At the end of this chapter you will be fa­mil­iar with about two dozen in­teg­ra­tion pat­terns.

                                                        整合的必要性

                                                        The Need for Integration

                                                        企业通常由数百个(如果不是数千个)应用程序组成,这些应用程序是定制的、从第三方获取的、遗留系统的一部分或其组合,在不同操作系统平台的多层中运行。拥有 30 个不同网站、三个 SAP 实例和无数部门解决方案的企业并不罕见。

                                                        En­ter­prises are typ­ic­ally com­prised of hun­dreds, if not thou­sands, of ap­plic­a­tions that are custom built, ac­quired from a third party, part of a legacy system, or a com­bin­a­tion thereof, op­er­at­ing in mul­tiple tiers of dif­fer­ent op­er­at­ing system plat­forms. It is not un­com­mon to find an en­ter­prise that has 30 dif­fer­ent Web sites, three in­stances of SAP, and count­less de­part­mental solu­tions.

                                                        我们可能会想问:企业如何让自己陷入如此混乱的境地?难道不应该解雇负责这种企业意大利式架构的 CIO 吗?嗯,就像在大多数情况下一样,事情的发生都是有原因的。

                                                        We may be temp­ted to ask: How do busi­nesses allow them­selves to get into such a mess? Shouldn't any CIO who is re­spons­ible for such an en­ter­prise spa­ghetti ar­chi­tec­ture be fired? Well, as in most cases, things happen for a reason.

                                                        首先,编写业务应用程序很困难。创建一个单一的大型应用程序来运行完整的业务几乎是不可能的。企业资源规划 (ERP) 供应商在创建比以往更大的业务应用程序方面取得了一些成功。但现实是,即使是 SAP、Oracle、Peoplesoft 等重量级企业也只能执行典型企业所需业务功能的一小部分。通过 ERP 系统是当今企业中最流行的集成点之一,我们可以很容易地看出这一点。

                                                        First, writ­ing busi­ness ap­plic­a­tions is hard. Cre­at­ing a single, big ap­plic­a­tion to run a com­plete busi­ness is next to im­pos­sible. The En­ter­prise Re­source Plan­ning (ERP) vendors have had some suc­cess at cre­at­ing larger-than-ever busi­ness ap­plic­a­tions. The real­ity, though, is that even the heavy­weights like SAP, Oracle, Peoplesoft, and the like per­form only a frac­tion of the busi­ness func­tions re­quired in a typ­ical en­ter­prise. We can see this easily by the fact that ERP sys­tems are one of the most pop­u­lar in­teg­ra­tion points in today's en­ter­prises.

                                                        其次,将业务功能分散到多个应用程序中,使企业能够根据其需求灵活地选择“最佳”会计软件包、“最佳”客户关系管理软件以及“最佳”订单处理系统。通常,IT 组织对完成所有工作的单个企业应用程序不感兴趣,考虑到单个业务需求的数量,这样的应用程序也是不可能的。

                                                        Second, spread­ing busi­ness func­tions across mul­tiple ap­plic­a­tions provides the busi­ness with the flex­ib­il­ity to select the "best" ac­count­ing pack­age, the "best" cus­tomer re­la­tion­ship man­age­ment soft­ware, as well as the "best" order-pro­cess­ing system for its needs. Usu­ally, IT or­gan­iz­a­tions are not in­ter­ested in a single en­ter­prise ap­plic­a­tion that does it all, nor is such an ap­plic­a­tion pos­sible given the number of in­di­vidual busi­ness re­quire­ments.

                                                        供应商已经学会迎合这种偏好,并围绕特定的核心功能提供有针对性的应用程序。然而,向现有软件包添加新功能的强烈愿望导致了打包业务应用程序之间的一些功能溢出。例如,许多计费系统开始纳入客户服务和会计功能。同样,客户服务软件制造商尝试实施简单的计费功能,例如争议或调整。在系统之间定义清晰的功能分离很困难:客户对账单的争议是否被视为客户服务或计费功能?

                                                        Vendors have learned to cater to this pref­er­ence and offer fo­cused ap­plic­a­tions around a spe­cific core func­tion. How­ever, the ever-present urge to add new func­tion­al­ity to ex­ist­ing soft­ware pack­ages has caused some func­tion­al­ity spillover among pack­aged busi­ness ap­plic­a­tions. For ex­ample, many billing sys­tems star­ted to in­cor­por­ate cus­tomer care and ac­count­ing func­tion­al­ity. Like­wise, the cus­tomer care soft­ware maker takes a stab at im­ple­ment­ing simple billing func­tions, such as dis­putes or ad­just­ments. De­fin­ing a clear, func­tional sep­ar­a­tion between sys­tems is dif­fi­cult: Is a cus­tomer dis­pute over a bill con­sidered a cus­tomer care or a billing func­tion?

                                                        客户、业务合作伙伴和内部用户等用户在与业务交互时通常不会考虑系统边界。他们执行业务功能,无论业务功能跨越多少个内部系统。例如,客户可以致电更改他或她的地址并查看是否收到最后一笔付款。在许多企业中,这个简单的请求可以跨越客户服务和计费系统。同样,客户下新订单可能需要许多系统的协调。企业需要验证客户 ID、验证客户的良好信誉、检查库存、履行订单、获取运输报价、计算销售税、发送账单等。此过程可以轻松跨越五个或六个不同的系统。

                                                        Users such as cus­tom­ers, busi­ness part­ners, and in­ternal users gen­er­ally do not think about system bound­ar­ies when they in­ter­act with a busi­ness. They ex­ecute busi­ness func­tions re­gard­less of how many in­ternal sys­tems the busi­ness func­tion cuts across. For ex­ample, a cus­tomer may call to change his or her ad­dress and see whether the last pay­ment was re­ceived. In many en­ter­prises, this simple re­quest can span across both the cus­tomer care and billing sys­tems. Like­wise, a cus­tomer pla­cing a new order may re­quire the co­ordin­a­tion of many sys­tems. The busi­ness needs to val­id­ate the cus­tomer ID, verify the cus­tomer's good stand­ing, check in­vent­ory, ful­fill the order, get a ship­ping quote, com­pute sales tax, send a bill, and so on. This pro­cess can easily span five or six dif­fer­ent sys­tems. From the cus­tomer's per­spect­ive, it is a single busi­ness trans­ac­tion.

                                                        为了支持通用业务流程和跨应用程序的数据共享,需要集成这些应用程序。应用集成需要在多个企业应用之间提供高效、可靠、安全的数据交换。

                                                        In order to sup­port common busi­ness pro­cesses and data shar­ing across ap­plic­a­tions, these ap­plic­a­tions need to be in­teg­rated. Ap­plic­a­tion in­teg­ra­tion needs to provide ef­fi­cient, re­li­able, and secure data ex­change between mul­tiple en­ter­prise ap­plic­a­tions.

                                                          集成挑战

                                                          Integration Challenges

                                                          不幸的是,企业集成并不是一件容易的事。根据定义,企业集成必须处理在不同位置的多个平台上运行的多个应用程序,这使得“简单集成”一词几乎是一个矛盾的说法。软件供应商提供企业应用程序集成(EAI)套件,该套件提供跨平台、跨语言集成以及与许多流行的打包业务应用程序接口的能力。然而,这种技术基础设施仅解决了集成复杂性的一小部分。集成的真正挑战涉及业务和技术问题。

                                                          Un­for­tu­nately, en­ter­prise in­teg­ra­tion is no easy task. By defin­i­tion, en­ter­prise in­teg­ra­tion has to deal with mul­tiple ap­plic­a­tions run­ning on mul­tiple plat­forms in dif­fer­ent loc­a­tions, making the term simple in­teg­ra­tion pretty much an oxy­moron. Soft­ware vendors offer En­ter­prise Ap­plic­a­tion In­teg­ra­tion (EAI) suites that provide cross-plat­form, cross-lan­guage in­teg­ra­tion as well as the abil­ity to in­ter­face with many pop­u­lar pack­aged busi­ness ap­plic­a­tions. How­ever, this tech­nical in­fra­struc­ture ad­dresses only a small por­tion of the in­teg­ra­tion com­plex­it­ies. The true chal­lenges of in­teg­ra­tion span far across busi­ness and tech­nical issues.

                                                          • 企业整合需要企业政治的重大转变。业务应用程序通常专注于特定的功能领域,例如客户关系管理 (CRM)、计费和财务。这似乎是康威著名定律的延伸:“设计系统的组织被迫生产出这些组织通信结构的副本的设计。” 许多 IT 团队都是根据这些相同的职能领域来组织的。成功的企业集成不仅需要在多个计算机系统之间建立通信,而且还需要在集成的企业应用程序中的业务单元和IT部门之间建立通信,

                                                          • En­ter­prise in­teg­ra­tion re­quires a sig­ni­fic­ant shift in cor­por­ate polit­ics. Busi­ness ap­plic­a­tions gen­er­ally focus on a spe­cific func­tional area, such as cus­tomer re­la­tion­ship man­age­ment (CRM), billing, and fin­ance. This seems to be an ex­ten­sion of Conway's famous law: "Or­gan­iz­a­tions which design sys­tems are con­strained to pro­duce designs which are copies of the com­mu­nic­a­tion struc­tures of these or­gan­iz­a­tions." Many IT groups are or­gan­ized in align­ment with these same func­tional areas. Suc­cess­ful en­ter­prise in­teg­ra­tion needs to es­tab­lish com­mu­nic­a­tion not only between mul­tiple com­puter sys­tems but also between busi­ness units and IT de­part­mentsin an in­teg­rated en­ter­prise ap­plic­a­tion, groups no longer con­trol a spe­cific ap­plic­a­tion be­cause each ap­plic­a­tion is now part of an over­all flow of in­teg­rated ap­plic­a­tions and ser­vices.

                                                          • 由于其范围广泛,集成工作通常会对业务产生深远的影响。一旦最关键业务功能的处理被纳入集成解决方案中,该解决方案的正常运行对于业务就变得至关重要。失败或行为不当的集成解决方案可能会因订单丢失、付款错误和客户不满而给企业造成数百万美元的损失。

                                                          • Be­cause of their wide scope, in­teg­ra­tion ef­forts typ­ic­ally have far-reach­ing im­plic­a­tions on the busi­ness. Once the pro­cess­ing of the most crit­ical busi­ness func­tions is in­cor­por­ated into an in­teg­ra­tion solu­tion, the proper func­tion­ing of that solu­tion be­comes vital to the busi­ness. A fail­ing or mis­be­hav­ing in­teg­ra­tion solu­tion can cost a busi­ness mil­lions of dol­lars in lost orders, mis­routed pay­ments, and dis­gruntled cus­tom­ers.

                                                          • 开发集成解决方案的一个重要限制是集成开发人员通常对参与的应用程序具有有限的控制能力。在大多数情况下,应用程序是遗留系统或打包应用程序,无法仅为了连接到集成解决方案而进行更改。这通常使集成开发人员处于必须弥补应用程序内部的缺陷或特性或应用程序之间的差异的境地。通常,在应用程序端点内实现部分解决方案会更容易,但由于政治或技术原因,该选项可能不可用。

                                                          • One im­port­ant con­straint of de­vel­op­ing in­teg­ra­tion solu­tions is the lim­ited amount of con­trol the in­teg­ra­tion de­ve­lopers typ­ic­ally have over the par­ti­cip­at­ing ap­plic­a­tions. In most cases, the ap­plic­a­tions are legacy sys­tems or pack­aged ap­plic­a­tions that cannot be changed just to be con­nec­ted to an in­teg­ra­tion solu­tion. This often leaves the in­teg­ra­tion de­ve­lopers in a situ­ation where they have to make up for de­fi­cien­cies or idio­syn­crasies inside the ap­plic­a­tions or dif­fer­ences between the ap­plic­a­tions. Often it would be easier to im­ple­ment part of the solu­tion inside the ap­plic­a­tion en­d­points, but for polit­ical or tech­nical reas­ons, that option may not be avail­able.

                                                          • 尽管对集成解决方案的需求广泛,但只有少数标准在该领域建立起来。XML、XSL 和 Web 服务的出现无疑标志着集成解决方案中基于标准的功能的最重大进步。然而,围绕 Web 服务的炒作也为市场新的碎片化提供了基础,导致一系列新的标准“扩展”和“解释”。这应该提醒我们,“符合标准”的产品之间缺乏互操作性是 CORBA 的主要障碍之一,而 CORBA 为系统集成提供了复杂的技术解决方案。

                                                          • Des­pite the wide­spread need for in­teg­ra­tion solu­tions, only a few stand­ards have es­tab­lished them­selves in this domain. The advent of XML, XSL, and Web ser­vices cer­tainly marks the most sig­ni­fic­ant ad­vance of stand­ards-based fea­tures in an in­teg­ra­tion solu­tion. How­ever, the hype around Web ser­vices has also given grounds to new frag­ment­a­tion of the mar­ket­place, res­ult­ing in a flurry of new "ex­ten­sions" and "in­ter­pret­a­tions" of the stand­ards. This should remind us that the lack of in­ter­op­er­ab­il­ity between "stand­ards-com­pli­ant" products was one of the major stum­bling blocks for CORBA, which offered a soph­ist­ic­ated tech­nical solu­tion for sys­tems in­teg­ra­tion.

                                                          • 现有的 XML Web 服务标准仅解决了集成挑战的一小部分。例如,经常声称 XML 是系统集成的通用语言就有些误导。将所有数据交换标准化为 XML 可以比作使用通用字母表(例如罗马字母表)编写所有文档。尽管字母表很常见,但它仍然被用来表示许多语言和方言,而这些语言和方言并不是所有读者都能轻易理解的。企业整合也是如此。通用表示(例如 XML)的存在并不意味着通用语义。“账户”的概念在每个参与系统中可以有许多不同的语义、内涵、约束和假设。

                                                          • Ex­ist­ing XML Web ser­vices stand­ards ad­dress only a frac­tion of the in­teg­ra­tion chal­lenges. For ex­ample, the fre­quent claim that XML is the lingua franca of system in­teg­ra­tion is some­what mis­lead­ing. Stand­ard­iz­ing all data ex­change to XML can be likened to writ­ing all doc­u­ments using a common al­pha­bet, such as the Roman al­pha­bet. Even though the al­pha­bet is common, it is still being used to rep­res­ent many lan­guages and dia­lects, which cannot be read­ily un­der­stood by all read­ers. The same is true in en­ter­prise in­teg­ra­tion. The ex­ist­ence of a common present­a­tion (e.g., XML) does not imply common se­mantics. The notion of "ac­count" can have many dif­fer­ent se­mantics, con­nota­tions, con­straints, and as­sump­tions in each par­ti­cip­at­ing system. Resolv­ing se­mantic dif­fer­ences between sys­tems proves to be a par­tic­u­larly dif­fi­cult and time-con­sum­ing task be­cause it in­volves sig­ni­fic­ant busi­ness and tech­nical de­cisions.

                                                          • 虽然开发 EAI 解决方案本身具有挑战性,但操作和维护此类解决方案可能更加艰巨。EAI 解决方案的技术组合和分布式特性使得部署、监控和故障排除任务变得复杂,需要多种技能组合。在大多数情况下,这些技能集分布在许多不同的个人中,或者甚至不存在于 IT 运营中。

                                                          • While de­vel­op­ing an EAI solu­tion is chal­len­ging in itself, op­er­at­ing and main­tain­ing such a solu­tion can be even more daunt­ing. The mix of tech­no­lo­gies and the dis­trib­uted nature of EAI solu­tions make de­ploy­ment, mon­it­or­ing, and troubleshoot­ing com­plex tasks that re­quire a com­bin­a­tion of skill sets. In most cases, these skill sets are spread across many dif­fer­ent in­di­vidu­als or do not even exist within IT op­er­a­tions.

                                                          任何经历过 EAI 部署的人都可以证明,EAI 解决方案是当今企业战略的关键组成部分,但它们使 IT 的生活变得更加困难,而不是更轻松。集成企业的高级愿景(由直通式处理、T+1、敏捷企业等术语定义)和具体实施(System.Messaging.XmlMessageFormatter 的参数是什么)之间还有很长的路要走再拿一次?)。

                                                          Anyone who has been through an EAI de­ploy­ment can attest that EAI solu­tions are a crit­ical com­pon­ent of today's en­ter­prise strategies­but they make IT life harder, not easier. It's a long way between the high-level vision of the in­teg­rated en­ter­prise (defined by terms such as straight-through-pro­cess­ing, T+1, agile en­ter­prise) and the nuts-and-bolts im­ple­ment­a­tions (What para­met­ers did System.Mes­saging.Xm­lMes­sage­Format­ter take again?).

                                                            集成模式如何提供帮助

                                                            How Integration Patterns Can Help

                                                            企业集成没有简单的答案。在我们看来,任何声称集成很容易的人一定非常聪明(或者至少比我们其他人聪明一点),非常无知(好吧,让我们说乐观),或者有经济利益让你相信集成很容易。

                                                            There are no simple an­swers for en­ter­prise in­teg­ra­tion. In our opin­ion, anyone who claims that in­teg­ra­tion is easy must be in­cred­ibly smart (or at least a good bit smarter than the rest of us), in­cred­ibly ig­nor­ant (okay, let's say op­tim­istic), or have a fin­an­cial in­terest in making you be­lieve that in­teg­ra­tion is easy.

                                                            尽管整合是一个广泛而困难的话题,但我们总是可以观察到一些人在这方面比其他人做得更好。这些人知道哪些其他人不知道的事情?由于没有“21 天自学整合”这样的东西(这本书肯定不是!),这些人不太可能知道整合的所有答案。然而,他们通常已经解决了足够多的集成问题,可以将新问题与之前解决的问题进行比较。他们知道问题的“模式”和相关的解决方案。随着时间的推移,他们通过反复试验或从其他经验丰富的集成架构师那里学习了这些模式。

                                                            Even though in­teg­ra­tion is a broad and dif­fi­cult topic, we can always ob­serve some people who are much better at it than others. What do these people know that others don't? Since there is no such thing as "Teach Your­self In­teg­ra­tion in 21 Days" (this book sure ain't!), it is un­likely that these people know all the an­swers to in­teg­ra­tion. How­ever, they have usu­ally solved enough in­teg­ra­tion prob­lems that they can com­pare new prob­lems to prior prob­lems they have solved. They know the "pat­terns" of prob­lems and as­so­ci­ated solu­tions. They learned these pat­terns over time by trial-and-error or from other ex­per­i­enced in­teg­ra­tion ar­chi­tects.

                                                            这些模式不是复制粘贴代码示例或收缩包装的组件,而是描述经常出现的问题的解决方案的建议。如果使用得当,集成模式可以帮助填补集成的高级愿景与实际系统实现之间的巨大差距。

                                                            The pat­terns are not copy-paste code samples or shrink-wrapped com­pon­ents, but rather nug­gets of advice that de­scribe solu­tions to fre­quently re­cur­ring prob­lems. Used prop­erly, the in­teg­ra­tion pat­terns can help fill the wide gap between the high-level vision of in­teg­ra­tion and the actual system im­ple­ment­a­tion.

                                                              广阔的集成世界

                                                              The Wide World of Integration

                                                              我们有意将集成的定义保留得非常广泛。对我们来说,这意味着连接计算机系统、公司或人员。虽然这个广泛的定义使我们可以方便地将我们感兴趣的任何内容放入本书中,但仔细研究一些最常见的集成场景会很有帮助。在许多集成项目的过程中,我们反复遇到以下六种类型的集成:

                                                              We in­ten­tion­ally left the defin­i­tion of in­teg­ra­tion very broad. To us it means con­nect­ing com­puter sys­tems, com­pan­ies, or people. While this broad defin­i­tion gives us the con­veni­ence of stick­ing whatever we find in­ter­est­ing into this book, it is help­ful to have a closer look at some of the most common in­teg­ra­tion scen­arios. Over the course of many in­teg­ra­tion pro­jects, we re­peatedly came across the fol­low­ing six types of in­teg­ra­tion:

                                                              • 信息门户

                                                              • In­form­a­tion portals

                                                              • 数据复制

                                                              • Data rep­lic­a­tion

                                                              • 共享业务功能

                                                              • Shared busi­ness func­tions

                                                              • 面向服务的架构

                                                              • Ser­vice-ori­ented ar­chi­tec­tures

                                                              • 分布式业务流程

                                                              • Dis­trib­uted busi­ness pro­cesses

                                                              • 企业对企业整合

                                                              • Busi­ness-to-busi­ness in­teg­ra­tion

                                                              此列表绝不是所有集成的完整分类,但它确实说明了集成架构师构建的解决方案类型。许多集成项目由多种类型的集成组合而成。例如,通常需要复制参考数据,以便将应用程序绑定到单个分布式业务流程中。

                                                              This list is by no means a com­plete tax­onomy of all things in­teg­ra­tion, but it does il­lus­trate the kind of solu­tions that in­teg­ra­tion ar­chi­tects build. Many in­teg­ra­tion pro­jects con­sist of a com­bin­a­tion of mul­tiple types of in­teg­ra­tion. For ex­ample, ref­er­ence data rep­lic­a­tion is often re­quired in order to tie ap­plic­a­tions into a single, dis­trib­uted busi­ness pro­cess.

                                                              信息门户

                                                              In­form­a­tion Portal

                                                              图形/01inf01.gif

                                                              许多业务用户必须访问多个系统才能回答特定问题或执行一项业务功能。例如,为了验证订单的状态,客户服务代表可能必须访问大型机上的订单管理系统并登录到管理通过 Web 下达的订单的系统。信息门户将来自多个源的信息聚合到单个显示中,以避免用户访问多个系统来获取信息。简单的信息门户将屏幕划分为多个区域,每个区域显示来自不同系统的信息。更复杂的系统在区域之间提供有限的交互;例如,当用户从区域 A 的列表中选择一个项目时,区域 B 刷新并显示有关所选项目的详细信息。其他门户提供更复杂的用户交互,并模糊了门户和集成应用程序之间的界限。

                                                              Many busi­ness users have to access more than one system to answer a spe­cific ques­tion or to per­form a single busi­ness func­tion. For ex­ample, to verify the status of an order, a cus­tomer ser­vice rep­res­ent­at­ive may have to access the order man­age­ment system on the main­frame plus log on to the system that man­ages orders placed over the Web. In­form­a­tion portals ag­greg­ate in­form­a­tion from mul­tiple sources into a single dis­play to avoid having the user access mul­tiple sys­tems for in­form­a­tion. Simple in­form­a­tion portals divide the screen into mul­tiple zones, each of which dis­plays in­form­a­tion from a dif­fer­ent system. More soph­ist­ic­ated sys­tems provide lim­ited in­ter­ac­tion between zones; for ex­ample, when a user se­lects an item from a list in zone A, zone B re­freshes with de­tailed in­form­a­tion about the se­lec­ted item. Other portals provide even more soph­ist­ic­ated user in­ter­ac­tion and blur the line between a portal and an in­teg­rated ap­plic­a­tion.

                                                              数据复制

                                                              Data Rep­lic­a­tion

                                                              图形/01inf02.gif

                                                              许多业务系统需要访问相同的数据。例如,客户的地址可能会用在客户服务系统(当客户致电更改地址时)、会计系统(计算销售税)、运输系统(为货物贴上标签)和计费系统(以发送发票)。其中许多系统都有自己的数据存储来存储与客户相关的信息。当客户致电更改其地址时,所有这些系统都需要更改其客户地址的副本。这可以通过实施基于数据复制的集成策略来实现。

                                                              Many busi­ness sys­tems re­quire access to the same data. For ex­ample, a cus­tomer's ad­dress may be used in the cus­tomer care system (when the cus­tomer calls to change it), the ac­count­ing system (to com­pute sales tax), the ship­ping system (to label the ship­ment), and the billing system (to send an in­voice). Many of these sys­tems have their own data stores to store cus­tomer-re­lated in­form­a­tion. When a cus­tomer calls to change his or her ad­dress, all these sys­tems need to change their copy of the cus­tomer's ad­dress. This can be ac­com­plished by im­ple­ment­ing an in­teg­ra­tion strategy based on data rep­lic­a­tion.

                                                              有许多不同的方法来实现数据复制。例如,一些数据库供应商在数据库中内置了复制功能;或者,我们可以将数据导出到文件中,然后将它们重新导入到其他系统,或者我们可以使用面向消息的中间件来传输消息内的数据记录。

                                                              There are many dif­fer­ent ways to im­ple­ment data rep­lic­a­tion. For ex­ample, some data­base vendors build rep­lic­a­tion func­tions into the data­base; al­tern­at­ively, we can export data into files and re-import them to the other system, or we can use mes­sage-ori­ented mid­dle­ware to trans­port data re­cords inside mes­sages.

                                                              共享业务功能

                                                              Shared Busi­ness Func­tion

                                                              图形/01inf03.gif

                                                              就像许多业务应用程序存储冗余数据一样,它们也倾向于实现冗余功能。多个系统可能需要检查社会安全号码是否有效、地址是否与指定的邮政编码匹配,或者特定商品是否有库存。将这些功能公开为共享业务功能是有意义的,该功能只需实现一次即可作为服务提供给其他系统。

                                                              In the same way that many busi­ness ap­plic­a­tions store re­dund­ant data, they also tend to im­ple­ment re­dund­ant func­tion­al­ity. Mul­tiple sys­tems may need to check whether a social-se­cur­ity number is valid, whether the ad­dress matches the spe­cified postal code, or whether a par­tic­u­lar item is in stock. It makes sense to expose these func­tions as a shared busi­ness func­tion that is im­ple­men­ted once and avail­able as a ser­vice to other sys­tems.

                                                              共享业务功能可以满足一些与数据复制相同的需求。例如,我们可以实现一个名为“获取客户地址”的业务功能,该功能允许其他系统在需要时请求客户的地址,而不是永久存储冗余副本。这两种方法之间的决定是由许多标准驱动的,例如我们对系统的控制量(调用共享函数通常比将数据加载到数据库更具侵入性)或变化率(地址可能会改变)。经常需要但很少改变)。

                                                              A shared busi­ness func­tion can ad­dress some of the same needs as data rep­lic­a­tion. For ex­ample, we could im­ple­ment a busi­ness func­tion called Get Cus­tomer Ad­dress that could allow other sys­tems to re­quest the cus­tomer's ad­dress when it is needed rather than per­man­ently store a re­dund­ant copy. The de­cision between these two ap­proaches is driven by a number of cri­teria, such as the amount of con­trol we have over the sys­tems (call­ing a shared func­tion is usu­ally more in­trus­ive than load­ing data into the data­base) or the rate of change (an ad­dress may be needed fre­quently but change very in­fre­quently).

                                                              面向服务的架构

                                                              Ser­vice-Ori­ented Ar­chi­tec­ture

                                                              图形/01inf04.gif

                                                              共享业务功能通常称为服务。服务是一种定义明确的功能,普遍可用并响应“服务消费者”的请求。一旦企业组装了有用服务的集合,管理服务就成为一项关键功能。首先,应用程序需要某种形式的服务目录,即所有可用服务的集中列表。其次,每个服务需要以应用程序可以与服务“协商”通信合同的方式描述其接口。服务发现和协商这两个功能是构成面向服务的体系结构 (SOA) 的关键要素。

                                                              Shared busi­ness func­tions are often re­ferred to as ser­vices. A ser­vice is a well-defined func­tion that is uni­ver­sally avail­able and re­sponds to re­quests from "ser­vice con­sumers." Once an en­ter­prise as­sembles a col­lec­tion of useful ser­vices, man­aging the ser­vices be­comes a crit­ical func­tion. First, ap­plic­a­tions need some form of ser­vice dir­ect­ory, a cent­ral­ized list of all avail­able ser­vices. Second, each ser­vice needs to de­scribe its in­ter­face in such a way that an ap­plic­a­tion can "ne­go­ti­ate" a com­mu­nic­a­tions con­tract with the ser­vice. These two func­tions, ser­vice dis­cov­ery and ne­go­ti­ation, are the key ele­ments that make up a ser­vice-ori­ented ar­chi­tec­ture (SOA).

                                                              SOA 模糊了集成和分布式应用程序之间的界限。可以使用其他应用程序提供的现有远程服务来开发新的应用程序。因此,调用服务可以认为是两个应用程序之间的集成。然而,大多数 SOA 提供的工具使调用外部服务几乎与调用本地方法一样简单(抛开性能考虑),因此在 SOA 之上开发应用程序的过程类似于构建分布式应用程序。

                                                              SOAs blur the line between in­teg­ra­tion and dis­trib­uted ap­plic­a­tions. A new ap­plic­a­tion can be de­ve­loped using ex­ist­ing remote ser­vices that may be provided by other ap­plic­a­tions. There­fore, call­ing a ser­vice can be con­sidered in­teg­ra­tion between the two ap­plic­a­tions. How­ever, most SOAs provide tools that make call­ing an ex­ternal ser­vice almost as simple as call­ing a local method (per­form­ance con­sid­er­a­tions aside), so that the pro­cess of de­vel­op­ing an ap­plic­a­tion on top of an SOA re­sembles build­ing a dis­trib­uted ap­plic­a­tion.

                                                              分布式业务流程

                                                              Dis­trib­uted Busi­ness Pro­cess

                                                              图形/01inf05.gif

                                                              集成的关键驱动因素之一是单个业务事务通常分布在许多不同的系统中。前面的例子向我们展示了一个简单的业务功能(例如下订单)可以轻松地触及六个系统。在大多数情况下,所有相关功能都包含在现有应用程序中。缺少的是这些应用程序之间的协调。因此,我们可以添加一个业务流程管理组件来管理跨多个现有系统的业务功能的执行。

                                                              One of the key drivers of in­teg­ra­tion is that a single busi­ness trans­ac­tion is often spread across many dif­fer­ent sys­tems. A pre­vi­ous ex­ample showed us that a simple busi­ness func­tion such as pla­cing an order can easily touch half a dozen sys­tems. In most cases, all rel­ev­ant func­tions are in­cor­por­ated inside ex­ist­ing ap­plic­a­tions. What is miss­ing is the co­ordin­a­tion between these ap­plic­a­tions. There­fore, we can add a busi­ness pro­cess man­age­ment com­pon­ent that man­ages the ex­e­cu­tion of a busi­ness func­tion across mul­tiple ex­ist­ing sys­tems.

                                                              SOA 和分布式业务之间的界限可能是模糊的。例如,您可以将所有相关业务功能公开为服务,然后在通过 SOA 访问所有服务的应用程序内对业务流程进行编码。

                                                              The bound­ar­ies between an SOA and a dis­trib­uted busi­ness can be fuzzy. For ex­ample, you could expose all rel­ev­ant busi­ness func­tions as ser­vices and then encode the busi­ness pro­cess inside an ap­plic­a­tion that ac­cesses all ser­vices via an SOA.

                                                              企业对企业整合

                                                              Busi­ness-to-Busi­ness In­teg­ra­tion

                                                              图形/01inf06.gif

                                                              到目前为止,我们主要考虑的是单个企业内部应用程序和业务功能之间的交互。在许多情况下,业务功能可以从外部供应商或业务合作伙伴处获得。例如,运输公司可以为客户提供计算运输成本或跟踪运输的服务。或者,企业可以使用外部提供商来计算销售税率。同样,业务合作伙伴之间也经常发生整合。顾客可以联系零售商询问商品的价格和库存情况。作为回应,零售商可以向供应商询问包含缺货商品的预期发货的状态。

                                                              So far, we have mainly con­sidered the in­ter­ac­tion between ap­plic­a­tions and busi­ness func­tions inside a single en­ter­prise. In many cases, busi­ness func­tions may be avail­able from out­side sup­pli­ers or busi­ness part­ners. For ex­ample, the ship­ping com­pany may provide a ser­vice for cus­tom­ers to com­pute ship­ping cost or track ship­ments. Or a busi­ness may use an out­side pro­vider to com­pute sales tax rates. Like­wise, in­teg­ra­tion fre­quently occurs between busi­ness part­ners. A cus­tomer may con­tact a re­tailer to in­quire on the price and avail­ab­il­ity of an item. In re­sponse, the re­tailer may ask the sup­plier for the status of an ex­pec­ted ship­ment that con­tains the out-of-stock item.

                                                              上述许多考虑因素同样适用于企业对企业的集成。然而,通过互联网或其他网络进行通信通常会引发与传输协议和安全性相关的新问题。此外,由于许多业务合作伙伴可能在电子“对话”中进行协作,因此标准化数据格式至关重要。

                                                              Many of the above con­sid­er­a­tions apply equally to busi­ness-to-busi­ness in­teg­ra­tion. How­ever, com­mu­nic­at­ing across the In­ter­net or some other net­work usu­ally raises new issues re­lated to trans­port pro­to­cols and se­cur­ity. Also, since many busi­ness part­ners may col­lab­or­ate in an elec­tronic "con­ver­sa­tion," stand­ard­ized data formats are crit­ic­ally im­port­ant.

                                                                松耦合

                                                                Loose Coupling

                                                                企业架构和集成中最流行的术语之一是松耦合。事实上,这是一个非常流行的术语,以至于 Doug Kaye 写了一整本书,以这个无处不在的概念为标题 [ Kaye ]。松耦合的好处早已为人所知,但由于 Web 服务架构的迅速普及,它们最近才成为人们关注的焦点。

                                                                One of the biggest buzzwords in en­ter­prise ar­chi­tec­ture and in­teg­ra­tion is loose coup­ling. It is in fact such a pop­u­lar term that Doug Kaye wrote a whole book titled after this ubi­quit­ous concept [Kaye]. The be­ne­fits of loose coup­ling have been known for quite some time now, but they have taken center stage more re­cently due to the sur­ging pop­ular­ity of Web ser­vices ar­chi­tec­tures.

                                                                松耦合背后的核心原则是减少两方(组件、应用程序、服务、程序、用户)在交换信息时对彼此所做的假设。两方对彼此和通用协议做出的假设越多,通信的效率就越高,但解决方案对中断或更改的容忍度就越低,因为双方彼此紧密耦合。

                                                                The core prin­ciple behind loose coup­ling is to reduce the as­sump­tions two parties (com­pon­ents, ap­plic­a­tions, ser­vices, pro­grams, users) make about each other when they ex­change in­form­a­tion. The more as­sump­tions two parties make about each other and the common pro­tocol, the more ef­fi­cient the com­mu­nic­a­tion can be, but the less tol­er­ant the solu­tion is of in­ter­rup­tions or changes be­cause the parties are tightly coupled to each other.

                                                                紧耦合的一个很好的例子是本地方法调用。在应用程序内调用本地方法基于被调用例程和调用例程之间的许多假设。两种方法都必须在同一进程(例如虚拟机)中运行并以相同语言编写(或至少使用通用中间语言或字节代码)。调用方法必须使用商定的数据类型传递确切数量的预期参数。通话是即时的;也就是说,被调用方法在调用方法调用后立即开始处理。同时,只有当被调用方法完成时,调用方法才会恢复处理(意味着调用是同步的)。处理将在调用方法中自动恢复,并紧随方法调用后的语句。方法之间的通信是立即且瞬时的,因此调用者和被调用方法都不必担心第三方窃听形式的安全漏洞。所有这些假设使得编写结构良好的应用程序变得非常容易,这些应用程序将功能划分为单独的方法以供其他方法调用。由此产生的大量小方法可以实现灵活性和重用。所有这些假设使得编写结构良好的应用程序变得非常容易,这些应用程序将功能划分为单独的方法以供其他方法调用。由此产生的大量小方法可以实现灵活性和重用。所有这些假设使得编写结构良好的应用程序变得非常容易,这些应用程序将功能划分为单独的方法以供其他方法调用。由此产生的大量小方法可以实现灵活性和重用。

                                                                A great ex­ample of tight coup­ling is a local method in­voc­a­tion. In­vok­ing a local method inside an ap­plic­a­tion is based on a lot of as­sump­tions between the called and the call­ing routine. Both meth­ods have to run in the same pro­cess (e.g., a vir­tual ma­chine) and be writ­ten in the same lan­guage (or at least use a common in­ter­me­di­ate lan­guage or byte code). The call­ing method has to pass the exact number of ex­pec­ted para­met­ers using agreed-upon data types. The call is im­me­di­ate; that is, the called method starts pro­cess­ing im­me­di­ately after the call­ing method makes the call. Mean­while, the call­ing method will resume pro­cess­ing only when the called method com­pletes (mean­ing the in­voc­a­tion is syn­chron­ous). Pro­cess­ing will auto­mat­ic­ally resume in the call­ing method with the state­ment im­me­di­ately fol­low­ing the method call. The com­mu­nic­a­tion between the meth­ods is im­me­di­ate and in­stant­an­eous, so neither the caller nor the called method has to worry about se­cur­ity breaches in the form of eaves­drop­ping third parties. All these as­sump­tions make it very easy to write well-struc­tured ap­plic­a­tions that divide func­tion­al­ity into in­di­vidual meth­ods to be called by other meth­ods. The res­ult­ing large number of small meth­ods allows for flex­ib­il­ity and reuse.

                                                                许多集成方法旨在通过将远程数据交换封装成与本地方法调用相同的语义来使远程通信变得简单。这种策略产生了远程过程调用(RPC)或远程方法调用(RMI)的概念,许多流行的框架和平台都支持这种概念:CORBA(参见 [ Zahavi])、Microsoft DCOM、.NET Remoting、Java RMI 以及最近的 RPC 风格的 Web 服务。这种方法的预期好处是双重的。首先,同步方法调用语义对于应用程序开发人员来说非常熟悉,那么为什么不建立在我们已经知道的基础上呢?其次,对本地方法调用和远程调用使用相同的语法和语义将使我们能够将哪些组件应在本地运行、哪些组件应远程运行的决定推迟到部署时,从而使应用程序开发人员少担心一件事。

                                                                Many in­teg­ra­tion ap­proaches have aimed to make remote com­mu­nic­a­tions simple by pack­aging a remote data ex­change into the same se­mantics as a local method call. This strategy res­ul­ted in the notion of a Remote Pro­ced­ure Call (RPC) or Remote Method In­voc­a­tion (RMI), sup­por­ted by many pop­u­lar frame­works and plat­forms: CORBA (see [Zahavi]), Mi­crosoft DCOM, .NET Re­mot­ing, Java RMI, and most re­cently, RPC-style Web ser­vices. The in­ten­ded upside of this ap­proach is two­fold. First, syn­chron­ous method-call se­mantics are very fa­mil­iar to ap­plic­a­tion de­ve­lopers, so why not build on what we already know? Second, using the same syntax and se­mantics for both local method calls and remote in­voc­a­tions would allow us to defer until de­ploy­ment time the de­cision about which com­pon­ents should run loc­ally and which should run re­motely, leav­ing the ap­plic­a­tion de­ve­loper with one less thing to worry about.

                                                                所有这些方法面临的挑战在于远程通信使本地方法调用所基于的许多假设无效。因此,将远程通信抽象为方法调用的简单语义可能会造成混乱和误导。Waldo 和同事早在 1994 年就提醒我们,“在分布式系统中交互的对象需要以与在单个地址空间中交互的对象本质上不同的方式进行处理”[ Waldo]。例如,如果我们调用远程服务来为我们执行某个功能,我们真的想将自己限制为仅使用与我们使用的相同编程语言构建的那些服务吗?通过网络进行的呼叫往往也比本地呼叫慢多个数量级。调用方法真的应该等到被调用方法完成吗?如果网络中断,调用的方法暂时无法访问怎么办?我们应该等多久?我们如何确保我们与目标方而不是第三方“欺骗者”进行通信?我们如何防止窃听?如果被调用方法的方法签名(预期参数列表)发生变化怎么办?如果远程方法由第三方或业务合作伙伴维护,我们不再能够控制此类变化。我们应该让我们的方法调用失败,还是应该尝试找到参数之间的最佳可能映射并仍然进行调用?很快就会发现,远程集成带来了许多本地方法调用从未处理过的问题。

                                                                The chal­lenge that all these ap­proaches face lies in the fact that remote com­mu­nic­a­tion in­val­id­ates many of the as­sump­tions that a local method call is based on. As a result, ab­stract­ing the remote com­mu­nic­a­tion into the simple se­mantics of a method call can be con­fus­ing and mis­lead­ing. Waldo and col­leagues re­minded us back in 1994 that "ob­jects that in­ter­act in a dis­trib­uted system need to be dealt with in ways that are in­trins­ic­ally dif­fer­ent from ob­jects that in­ter­act in a single ad­dress space" [Waldo]. For ex­ample, if we call a remote ser­vice to per­form a func­tion for us, do we really want to re­strict ourselves to only those ser­vices that were built using the same pro­gram­ming lan­guage we use? A call across the net­work also tends to be mul­tiple orders of mag­nitude slower than a local call. Should the call­ing method really wait until the called method com­pletes? What if the net­work is in­ter­rup­ted and the called method is tem­por­ar­ily un­reach­able? How long should we wait? How can we be sure we com­mu­nic­ate with the in­ten­ded party and not a third-party "spoofer"? How can we pro­tect against eaves­drop­ping? What if the method sig­na­ture (the list of ex­pec­ted para­met­ers) of the called method changes? If the remote method is main­tained by a third party or a busi­ness part­ner, we no longer have con­trol over such changes. Should we have our method in­voc­a­tion fail, or should we at­tempt to find the best pos­sible map­ping between the para­met­ers and still make the call? It quickly be­comes ap­par­ent that remote in­teg­ra­tion brings up a lot of issues that a local method call never had to deal with.

                                                                总之,试图将远程通信描述为本地方法调用的变体是自找麻烦。这种紧密耦合的架构通常会导致解决方案脆弱、难以维护且可扩展性差。许多 Web 服务先驱最近(重新)艰难地发现了这一事实。

                                                                In sum­mary, trying to por­tray remote com­mu­nic­a­tion as a vari­ant of a local method in­voc­a­tion is asking for trouble. Such tightly coupled ar­chi­tec­tures typ­ic­ally result in brittle, hard-to-main­tain, and poorly scal­able solu­tions. Many Web ser­vices pi­on­eers re­cently (re-)dis­covered this fact the hard way.

                                                                  一分钟 EAI

                                                                  One-Minute EAI

                                                                  为了展示紧密耦合依赖关系的影响以及如何解决它们,让我们看一下连接两个系统的非常简单的方法。假设我们正在构建一个网上银行系统,允许客户从另一家银行将钱存入他们的帐户。为了执行此功能,前端 Web 应用程序必须与管理资金转移的后端财务系统集成。

                                                                  To show the ef­fects of tightly coupled de­pend­en­cies and how to re­solve them, let's look at a very simple way of con­nect­ing two sys­tems. Let's assume we are build­ing an online bank­ing system that allows cus­tom­ers to de­posit money into their ac­count from an­other bank. To per­form this func­tion, the front-end Web ap­plic­a­tion has to be in­teg­rated with the back-end fin­an­cial system that man­ages fund trans­fers.

                                                                  连接两个系统最简单的方法是通过 TCP/IP 协议。过去 15 年中创建的每一个有自尊的操作系统或编程库都肯定包含 TCP/IP 堆栈。TCP/IP 是一种普遍存在的通信协议,可在连接到 Internet 和本地网络的数百万台计算机之间传输数据。为什么不使用最普遍的网络协议在两个应用程序之间进行通信?

                                                                  The easi­est way to con­nect the two sys­tems is through the TCP/IP pro­tocol. Every self-re­spect­ing op­er­at­ing system or pro­gram­ming lib­rary cre­ated in the last 15 years is cer­tain to in­clude a TCP/IP stack. TCP/IP is the ubi­quit­ous com­mu­nic­a­tions pro­tocol that trans­ports data between the mil­lions of com­puters con­nec­ted to the In­ter­net and local net­works. Why not use the most ubi­quit­ous of all net­work pro­to­cols to com­mu­nic­ate between two ap­plic­a­tions?

                                                                  为了简单起见,我们假设将钱存入某人帐户的远程函数仅使用此人的姓名和美元金额作为参数。以下几行代码足以通过 TCP/IP 调用此类函数(我们选择了 C#,但此代码在 C 或 Java 中看起来几乎相同)。

                                                                  To keep things simple, let's assume that the remote func­tion that de­pos­its money into a person's ac­count takes only the person's name and the dollar amount as ar­gu­ments. The fol­low­ing few lines of code suf­fice to call such a func­tion over TCP/IP (we chose C#, but this code would look vir­tu­ally identical in C or Java).

                                                                  String 主机名 = "finance.bank.com";
                                                                  国际端口=80;
                                                                  
                                                                  IPHostEntry 主机信息 = Dns.GetHostByName(主机名);
                                                                  IPAddress 地址 = hostInfo.AddressList[0];
                                                                  
                                                                  IPEndPoint 端点 = new IPEndPoint(地址, 端口);
                                                                  
                                                                  Socket套接字 = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                                                                  套接字.Connect(端点);
                                                                  
                                                                  byte[] 金额 = BitConverter.GetBytes(1000);
                                                                  byte[] name = Encoding.ASCII.GetBytes("Joe");
                                                                  
                                                                  int bytesSent = socket.Send(金额);
                                                                  bytesSent += socket.Send(名称);
                                                                  
                                                                  套接字.关闭();
                                                                  
                                                                  String host­Name = "fin­ance.bank.com";
                                                                  int port = 80;
                                                                  
                                                                  IPHos­tEn­try hostInfo = Dns.GetH­ost­By­Name(host­Name);
                                                                  IPAd­dress ad­dress = hostInfo.Ad­dress­List[0];
                                                                  
                                                                  IPEnd­Point en­d­point = new IPEnd­Point(ad­dress, port);
                                                                  
                                                                  Socket socket = new Socket(ad­dress.Ad­dress­Fam­ily, Sock­et­Type.Stream, Pro­to­col­Type.Tcp);
                                                                  socket.Con­nect(en­d­point);
                                                                  
                                                                  byte[] amount = Bit­Con­verter.Get­Bytes(1000);
                                                                  byte[] name   = En­cod­ing.ASCII.Get­Bytes("Joe");
                                                                  
                                                                  int bytes­Sent = socket.Send(amount);
                                                                  bytes­Sent    += socket.Send(name);
                                                                  
                                                                  socket.Close();
                                                                  

                                                                  此代码打开到地址Finance.bank.com 的套接字连接,并通过网络发送两个数据项(金额和客户姓名)。不需要昂贵的中间件:不需要EAI工具,RPC工具包只需10行代码。当我们运行此代码时,它告诉我们“已发送 7 个字节”。瞧!整合怎么会那么困难呢?

                                                                  This code opens a socket con­nec­tion to the ad­dress fin­ance.bank.com and sends two data items (the amount and the cus­tomer's name) across the net­work. No ex­pens­ive mid­dle­ware is re­quired: no EAI tools, RPC toolkit­s­just 10 lines of code. When we run this code, it tells us "7 bytes sent." Voila! How can in­teg­ra­tion be dif­fi­cult?

                                                                  这种集成尝试存在几个主要问题。TCP/IP 协议的优势之一是其广泛的支持,因此我们可以连接到几乎任何连接到网络的计算机,无论其使用什么操作系统或编程语言。然而,平台无关性仅适用于非常简单的消息:字节流。为了将数据转换为字节流,我们使用了BitConverter班级。此类使用数据类型的内部存储器表示形式将任何数据类型转换为字节数组。问题是整数的内部表示因计算机系统而异。例如,.NET 使用 32 位整数,而其他系统可能使用 64 位表示。我们的示例通过网络传输 4 个字节来表示 32 位整数。使用 64 位的系统倾向于从网络读取 8 个字节,最终将整个消息(包括客户名称)解释为单个数字。

                                                                  There are a couple of major prob­lems with this in­teg­ra­tion at­tempt. One of the strengths of the TCP/IP pro­tocol is its wide sup­port so that we can con­nect to pretty much any com­puter con­nec­ted to the net­work re­gard­less of the op­er­at­ing system or pro­gram­ming lan­guage it uses. How­ever, the plat­form-in­de­pend­ence works only for very simple mes­sages: byte streams. In order to con­vert our data into a byte stream, we used the Bit­Con­verter class. This class con­verts any data type into a byte array, using the in­ternal memory rep­res­ent­a­tion of the data type. The catch is that the in­ternal rep­res­ent­a­tion of an in­teger number varies with com­puter sys­tems. For ex­ample, .NET uses a 32-bit in­teger, while other sys­tems may use a 64-bit rep­res­ent­a­tion. Our ex­ample trans­fers 4 bytes across the net­work to rep­res­ent a 32-bit in­teger number. A system using 64 bits would be in­clined to read 8 bytes off the net­work and would end up in­ter­pret­ing the whole mes­sage (in­clud­ing the cus­tomer name) as a single number.

                                                                  此外,一些计算机系统以大端格式存储数字,而另一些计算机系统以小端格式存储数字。大端格式首先存储从最高字节开始的数字,而小端系统首先存储最低字节。PC 采用小端模式运行,因此代码通过网络传递以下 4 个字节:

                                                                  Also, some com­puter sys­tems store their num­bers in big-endian format, while others store them in little-endian format. A big-endian format stores num­bers start­ing with the highest byte first, while little-endian sys­tems store the lowest byte first. PCs op­er­ate on a little-endian scheme so that the code passes the fol­low­ing 4 bytes across the net­work:

                                                                  232 3 0 0
                                                                  
                                                                  232  3  0  0
                                                                  

                                                                  232 + 3 * 2 8等于 1,000。使用大端数字的系统会认为此消息表示 232 * 2 24 + 3 * 2 16 = 3,892,510,720。乔将成为一个非常富有的人!因此,这种方法仅在所有连接的计算机以相同内部格式表示数字的假设下才有效。

                                                                  232 + 3 * 28 equals 1,000. A system that uses big-endian num­bers would con­sider this mes­sage to mean 232 * 224 + 3 * 216 = 3,892,510,720. Joe will be a very rich man! So this ap­proach works only under the as­sump­tion that all con­nec­ted com­puters rep­res­ent num­bers in the same in­ternal format.

                                                                  这种简单方法的第二个问题是我们指定远程计算机的位置(在我们的例子中为finance.bank.com)。动态命名服务 (DNS) 为我们提供了域名和 IP 地址之间的一级间接寻址,但如果我们想将该功能移至不同域中的另一台计算机上该怎么办?如果机器出现故障并且我们必须设置另一台机器怎么办?如果我们想将信息发送到多台机器怎么办?对于每种情况,我们都必须更改代码。如果我们使用很多远程功能,这可能会变得非常乏味。因此,我们应该找到一种方法,使我们的通信独立于网络上的特定机器。

                                                                  The second prob­lem with this simple ap­proach is that we spe­cify the loc­a­tion of the remote ma­chine (in our case, fin­ance.bank.com). The Dy­namic Naming Ser­vice (DNS) gives us one level of in­dir­ec­tion between the domain name and the IP ad­dress, but what if we want to move the func­tion to a dif­fer­ent com­puter on a dif­fer­ent domain? What if the ma­chine fails and we have to set up an­other ma­chine? What if we want to send the in­form­a­tion to more than one ma­chine? For each scen­ario, we would have to change the code. If we use a lot of remote func­tions, this could become very te­di­ous. So, we should find a way to make our com­mu­nic­a­tion in­de­pend­ent from a spe­cific ma­chine on the net­work.

                                                                  我们的简单 TCP/IP 示例还在两台机器之间建立了时间依赖性。TCP/IP 是一种面向连接的协议。在传输任何数据之前,必须首先建立连接。建立 TCP 连接涉及 IP 数据包在发送方和接收方之间来回传输。这就要求机器和网络同时可用。如果这三个部分中的任何一个出现故障或由于高负载而无法使用,则无法发送数据。

                                                                  Our simple TCP/IP ex­ample also es­tab­lishes tem­poral de­pend­en­cies between the two ma­chines. TCP/IP is a con­nec­tion-ori­ented pro­tocol. Before any data can be trans­ferred, a con­nec­tion has to be es­tab­lished first. Es­tab­lish­ing a TCP con­nec­tion in­volves IP pack­ets trav­el­ing back and forth between sender and re­ceiver. This re­quires that both ma­chines and the net­work are all avail­able at the same time. If any of the three pieces is mal­func­tion­ing or not avail­able due to high load, the data cannot be sent.

                                                                  最后,简单的通信还依赖于非常严格的数据格式。我们发送 4 个字节的金额数据,然后发送定义客户帐户的字符序列。如果我们想插入第三个参数,例如货币名称,我们必须修改发送者和接收者以使用新的数据格式。

                                                                  Fi­nally, the simple com­mu­nic­a­tion also relies on a very strict data format. We are send­ing 4 bytes of amount data and then a se­quence of char­ac­ters that define the cus­tomer's ac­count. If we want to insert a third para­meter, such as the name of the cur­rency, we would have to modify both sender and re­ceiver to use the new data format.

                                                                  紧耦合交互

                                                                  Tightly Coupled In­ter­ac­tion

                                                                  图形/01inf07.gif

                                                                  我们的极简集成解决方案既快速又便宜,但它会导致一个非常脆弱的解决方案,因为两个参与方对彼此做出以下假设:

                                                                  Our min­im­al­ist in­teg­ra­tion solu­tion is fast and cheap, but it res­ults in a very brittle solu­tion be­cause the two par­ti­cip­at­ing parties make the fol­low­ing as­sump­tions about each other:

                                                                  • 数字和对象的平台技术 内部表示

                                                                  • Plat­form tech­no­logy in­ternal rep­res­ent­a­tions of num­bers and ob­jects

                                                                  • 位置 硬编码机器地址

                                                                  • Loc­a­tion hard­coded ma­chine ad­dresses

                                                                  • 所有组件必须同时可用的时间

                                                                  • Time all com­pon­ents have to be avail­able at the same time

                                                                  • 数据格式参数 列表及其类型必须匹配

                                                                  • Data format the list of para­met­ers and their types must match

                                                                  正如我们之前所说,耦合是衡量各方在沟通时对彼此做出多少假设的指标。我们的简单解决方案需要各方做出很多假设。因此,该解决方案是紧耦合的。

                                                                  As we stated earlier, coup­ling is a meas­ure of how many as­sump­tions parties make about each other when they com­mu­nic­ate. Our simple solu­tion re­quires the parties to make a lot of as­sump­tions. There­fore, this solu­tion is tightly coupled.

                                                                  为了使解决方案更加松散耦合,我们可以尝试将这些依赖项一一删除。我们应该使用一种自描述且与平台无关的标准数据格式,例如XML。我们不应该将信息直接发送到特定的机器,而应该将其发送到可寻址的通道。通道是发送者和接收者可以在不知道彼此身份的情况下达成一致的逻辑地址。使用通道可以解决位置依赖性,但如果通道是使用面向连接的协议实现的,则仍然需要所有组件同时可用。为了消除这种时间依赖性,我们可以增强通道以对发送的请求进行排队,直到网络和接收系统准备就绪。发送者现在可以将请求发送到通道中并继续处理,而不必担心数据的传递。在通道内对请求进行排队需要将数据分块为独立的消息以便通道知道在任一时间要缓冲和传送多少数据。这两个系统仍然依赖于通用的数据格式,但我们可以通过允许通道内的数据格式转换来消除这种依赖性。如果一个系统的格式发生变化,我们只需要改变变压器,而不需要改变其他参与的系统。如果许多应用程序将数据发送到同一通道,这尤其有用

                                                                  In order to make the solu­tion more loosely coupled, we can try to remove these de­pend­en­cies one by one. We should use a stand­ard data format that is self-de­scrib­ing and plat­form-in­de­pend­ent, such as XML. In­stead of send­ing in­form­a­tion dir­ectly to a spe­cific ma­chine, we should send it to an ad­dress­able chan­nel. A chan­nel is a lo­gical ad­dress that both sender and re­ceiver can agree on without being aware of each other's iden­tity. Using chan­nels re­solves the loc­a­tion de­pend­ency but still re­quires all com­pon­ents to be avail­able at the same time if the chan­nel is im­ple­men­ted using a con­nec­tion-ori­ented pro­tocol. In order to remove this tem­poral de­pend­ency, we can en­hance the chan­nel to queue up sent re­quests until the net­work and the re­ceiv­ing system are ready. The sender can now send re­quests into the chan­nel and con­tinue pro­cess­ing without having to worry about the de­liv­ery of the data. Queuing re­quests inside the chan­nel re­quires data to be chunked into self-con­tained mes­sages so that the chan­nel knows how much data to buffer and de­liver at any one time. The two sys­tems still depend on a common data format, but we can remove this de­pend­ency by al­low­ing for data format trans­form­a­tions inside the chan­nel. If the format of one system changes, we only have to change the trans­former and not the other par­ti­cip­at­ing sys­tems. This is par­tic­u­larly useful if many ap­plic­a­tions send data to the same chan­nel

                                                                  松耦合交互

                                                                  Loosely Coupled In­ter­ac­tion

                                                                  图形/01inf08.gif

                                                                  通用数据格式、跨队列通道的异步通信和变压器等机制有助于将紧密耦合的解决方案转变为松散耦合的解决方案。发送方不再需要依赖接收方的内部数据格式或其位置。它甚至不必关注另一台计算机是否准备好接受请求。消除系统之间的这些依赖关系使整体解决方案更能容忍变化,这是松散耦合的主要好处。松散耦合方法的主要缺点是增加了复杂性。这不再是 10 行代码的解决方案!因此,我们使用面向消息的中间件为我们提供这些服务的基础设施。这种基础设施使得以松散耦合的方式交换数据几乎与我们开始的示例一样简单。下一节将介绍构成此类中间件解决方案的组件。

                                                                  Mech­an­isms such as a common data format, asyn­chron­ous com­mu­nic­a­tion across queuing chan­nels, and trans­formers help turn a tightly coupled solu­tion into a loosely coupled one. The sender no longer has to depend on the re­ceiver's in­ternal data format nor on its loc­a­tion. It does not even have to pay at­ten­tion to whether or not the other com­puter is ready to accept re­quests. Re­mov­ing these de­pend­en­cies between the sys­tems makes the over­all solu­tion more tol­er­ant to change, the key be­ne­fit of loose coup­ling. The main draw­back of the loosely coupled ap­proach is the ad­di­tional com­plex­ity. This is no longer a 10-lines-of-code solu­tion! There­fore, we use a mes­sage-ori­ented mid­dle­ware in­fra­struc­ture that provides these ser­vices for us. This in­fra­struc­ture makes ex­chan­ging data in a loosely coupled way almost as easy as the ex­ample we star­ted with. The next sec­tion de­scribes the com­pon­ents that make up such a mid­dle­ware solu­tion.

                                                                  松耦合是万能药吗?与企业架构中的其他事物一样,没有单一的最佳答案。松耦合提供了灵活性和可扩展性等重要优势,但它引入了更复杂的编程模型,并使设计、构建和调试解决方案变得更加困难。

                                                                  Is loose coup­ling the pan­acea? Like everything else in en­ter­prise ar­chi­tec­ture, there is no single best answer. Loose coup­ling provides im­port­ant be­ne­fits such as flex­ib­il­ity and scalab­il­ity, but it in­tro­duces a more com­plex pro­gram­ming model and can make design­ing, build­ing, and de­bug­ging solu­tions more dif­fi­cult.

                                                                    松耦合集成解决方案

                                                                    A Loosely Coupled Integration Solution

                                                                    为了通过集成解决方案连接两个系统,必须解决许多问题。这些功能构成了我们所说的中间件,即应用程序之间的粘合剂。

                                                                    In order to con­nect two sys­tems via an in­teg­ra­tion solu­tion, a number of issues have to be re­solved. These func­tions make up what we call mid­dle­warethe glue that sits between ap­plic­a­tions.

                                                                    某些数据总是必须从一个应用程序传输到下一个应用程序。该数据可能是需要复制的地址记录、对远程服务的调用或前往门户显示的 HTML 片段。无论有效负载如何,这段数据都需要被两端理解并且需要通过网络传输。两个元件提供了这一基本功能。我们需要一种可以将信息从一个应用程序转移到另一个应用程序的通信渠道该通道可以由一系列 TCP/IP 连接、共享文件、共享数据库或从一台计算机传送到另一台计算机的软盘(臭名昭著的“sneakernet”)组成。在此频道内,我们放置一条消息对要集成的两个应用程序具有商定含义的数据片段。这条数据可以非常小,例如已更改的单个客户的电话号码,也可以非常大,例如所有客户及其关联地址的完整列表。

                                                                    In­vari­ably, some data has to be trans­por­ted from one ap­plic­a­tion to the next. This data could be an ad­dress record that needs to be rep­lic­ated, a call to a remote ser­vice, or a snip­pet of HTML headed for a portal dis­play. Re­gard­less of the pay­load, this piece of data needs to be un­der­stood by both ends and needs to be trans­por­ted across a net­work. Two ele­ments provide this basic func­tion. We need a com­mu­nic­a­tions chan­nel that can move in­form­a­tion from one ap­plic­a­tion to the other. This chan­nel could con­sist of a series of TCP/IP con­nec­tions, a shared file, a shared data­base, or a floppy disk being car­ried from one com­puter to the next (the in­fam­ous "sneak­er­net"). Inside this chan­nel, we place a mes­sagea snip­pet of data that has an agreed-upon mean­ing to both ap­plic­a­tions that are to be in­teg­rated. This piece of data can be very small, such as the phone number of a single cus­tomer that has changed, or it can be very large, such as the com­plete list of all cus­tom­ers and their as­so­ci­ated ad­dresses.

                                                                    基于消息的集成的基本要素

                                                                    Basic Ele­ments of Mes­sage-Based In­teg­ra­tion

                                                                    图形/01inf09.gif

                                                                    现在我们可以跨渠道发送消息,我们可以建立一种非常基本的集成形式。然而,我们承诺简单的集成是一个矛盾的说法,所以让我们看看缺少什么。我们提到集成解决方案通常对其所集成的应用程序具有有限的控制,例如应用程序使用的内部数据格式。例如,一种数据格式可能将客户姓名存储在两个字段中,称为FIRST_NAMELAST_NAME ,而另一种系统可能使用称为Customer_Name的单个字段。同样,一个系统可能支持多个客户地址,而另一系统仅支持单个地址。由于应用程序的内部数据格式通常无法更改,因此中间件需要提供某种机制来将一个应用程序的数据格式转换为另一个应用程序的数据格式。我们称此步骤为翻译

                                                                    Now that we can send mes­sages across chan­nels, we can es­tab­lish a very basic form of in­teg­ra­tion. How­ever, we prom­ised that simple in­teg­ra­tion is an oxy­moron, so let's see what is miss­ing. We men­tioned that in­teg­ra­tion solu­tions often have lim­ited con­trol over the ap­plic­a­tions they are in­teg­rat­ing, such as the in­ternal data formats used by the ap­plic­a­tions. For ex­ample, one data format may store the cus­tomer name in two fields, called FIRST_­NAME and LAST_­NAME, while the other system may use a single field called Cus­tom­er­_­Name. Like­wise, one system may sup­port mul­tiple cus­tomer ad­dresses, while the other system sup­ports only a single ad­dress. Be­cause the in­ternal data format of an ap­plic­a­tion can often not be changed, the mid­dle­ware needs to provide some mech­an­ism to con­vert one ap­plic­a­tion's data format in the other's format. We call this step trans­la­tion.

                                                                    到目前为止,我们可以将数据从一个系统发送到另一个系统,并适应数据格式的差异。如果我们集成两个以上的系统会发生什么?数据必须移动到哪里?我们可以期望每个应用程序为其通过通道发送的数据指定目标系统。例如,如果客户服务系统中的客户地址发生变化,我们可以让该系统负责将数据发送到存储客户地址副本的所有其他系统。随着系统数量的增加,这变得非常乏味,并且需要发送系统了解所有其他系统。每次添加新系统时,客户服务系统都必须调整以适应新环境。如果中间件可以负责将消息发送到正确的位置,事情就会容易得多。这是一个角色路由组件,例如消息代理。

                                                                    So far, we can send data from one system to an­other and ac­com­mod­ate dif­fer­ences in data formats. What hap­pens if we in­teg­rate more than two sys­tems? Where does the data have to be moved? We could expect each ap­plic­a­tion to spe­cify the target system(s) for the data it is send­ing over the chan­nel. For ex­ample, if the cus­tomer ad­dress changes in the cus­tomer care system, we could make that system re­spons­ible for send­ing the data to all other sys­tems that store copies of the cus­tomer ad­dress. As the number of sys­tems in­creases, this be­comes very te­di­ous and re­quires the send­ing system to have know­ledge about all other sys­tems. Every time a new system is added, the cus­tomer care system would have to be ad­jus­ted to the new en­vir­on­ment. Things would be a lot easier if the mid­dle­ware could take care of send­ing mes­sages to the cor­rect places. This is the role of a rout­ing com­pon­ent, such as a mes­sage broker.

                                                                    集成解决方案很快就会变得复杂,因为它们处理多种应用程序、数据格式、通道、路由和转换。所有这些元素可能分布在多个操作平台和地理位置。为了了解系统内部发生了什么,我们需要系统管理功能。该子系统监视数据流,确保所有应用程序和组件都启动并运行,并向中央位置报告错误情况。

                                                                    In­teg­ra­tion solu­tions can quickly become com­plex be­cause they deal with mul­tiple ap­plic­a­tions, data formats, chan­nels, rout­ing, and trans­form­a­tion. All these ele­ments may be spread across mul­tiple op­er­at­ing plat­forms and geo­graphic loc­a­tions. In order to have any idea what is going on inside the system, we need a sys­tems man­age­ment func­tion. This sub­sys­tem mon­it­ors the flow of data, makes sure that all ap­plic­a­tions and com­pon­ents are up and run­ning, and re­ports error con­di­tions to a cent­ral loc­a­tion.

                                                                    我们的集成解决方案现在已基本完成。我们可以将数据从一个系统转移到另一个系统,适应数据格式的差异,将数据路由到所需的系统,并监控解决方案的性能。到目前为止,我们假设应用程序将数据作为消息发送到通道。然而,大多数打包应用程序和遗留应用程序以及许多自定义应用程序都没有准备好参与集成解决方案。我们需要一个消息端点来将系统显式连接到集成解决方案。端点可以是一段特殊的代码,也可以是集成软件供应商提供的通道适配器。

                                                                    Our in­teg­ra­tion solu­tion is now almost com­plete. We can move data from one system from an­other, ac­com­mod­ate dif­fer­ences in the data format, route the data to the re­quired sys­tems, and mon­itor the per­form­ance of the solu­tion. So far, we as­sumed that an ap­plic­a­tion sends data as a mes­sage to the chan­nel. How­ever, most pack­aged and legacy ap­plic­a­tions and many custom ap­plic­a­tions are not pre­pared to par­ti­cip­ate in an in­teg­ra­tion solu­tion. We need a mes­sage en­d­point to con­nect the system ex­pli­citly to the in­teg­ra­tion solu­tion. The en­d­point can be a spe­cial piece of code or a Chan­nel Ad­apter provided by an in­teg­ra­tion soft­ware vendor.

                                                                      Widgets & Gadgets 'R Us:一个例子

                                                                      Widgets & Gadgets 'R Us: An Example

                                                                      了解基于消息的集成解决方案的最佳方法是通过一个具体示例。让我们考虑一下 Widgets & Gadgets 'R Us (WGRUS),这是一家在线零售商,从制造商那里购买小部件和小工具并将其转售给客户。

                                                                      The best way to un­der­stand mes­sage-based in­teg­ra­tion solu­tions is by walk­ing through a con­crete ex­ample. Let's con­sider Wid­gets & Gad­gets 'R Us (WGRUS), an online re­tailer that buys wid­gets and gad­gets from man­u­fac­tur­ers and re­sells them to cus­tom­ers.

                                                                      WGRUS 生态系统

                                                                      WGRUS Eco­sys­tem

                                                                      图形/01inf10.gif

                                                                      对于此示例,我们假设解决方案需要支持以下要求。当然,为了简洁起见,我们对需求进行了一些简化,但尽管如此,此类需求在实际业务中还是经常出现。

                                                                      For this ex­ample, we assume that the solu­tion needs to sup­port the fol­low­ing re­quire­ments. Nat­ur­ally, we sim­pli­fied the re­quire­ments a bit for sake of brev­ity, but nev­er­the­less these types of re­quire­ments occur fre­quently in real busi­nesses.

                                                                      • 接受订单: 客户可以通过网络、电话或传真下订单。

                                                                      • Take Orders: Cus­tom­ers can place orders via Web, phone, or fax.

                                                                      • 处理订单: 处理订单涉及多个步骤,包括验证库存、运送货物以及向客户开具发票。

                                                                      • Pro­cess Orders: Pro­cess­ing an order in­volves mul­tiple steps, in­clud­ing veri­fy­ing in­vent­ory, ship­ping the goods, and in­voicing the cus­tomer.

                                                                      • 查看状态: 客户可以查看订单状态。

                                                                      • Check Status: Cus­tom­ers can check the order status.

                                                                      • 更改地址: 客户可以使用 Web 前端更改其帐单和送货地址。

                                                                      • Change Ad­dress: Cus­tom­ers can use a Web front-end to change their billing and ship­ping ad­dress.

                                                                      • 新目录: 供应商定期更新其目录。WGRUS 需要根据新目录更新其定价和供货情况。

                                                                      • New Cata­log: The sup­pli­ers update their cata­log peri­od­ic­ally. WGRUS needs to update its pri­cing and avail­ab­il­ity based on the new cata­logs.

                                                                      • 公告: 客户可以订阅 WGRUS 的选择性公告。

                                                                      • An­nounce­ments: Cus­tom­ers can sub­scribe to se­lect­ive an­nounce­ments from WGRUS.

                                                                      • 测试和监控: 操作人员需要能够监控所有单独的组件以及它们之间的消息流。

                                                                      • Test­ing and Mon­it­or­ing: The op­er­a­tions staff needs to be able to mon­itor all in­di­vidual com­pon­ents and the mes­sage flow between them.

                                                                      我们分别解决每个需求,并使用本书中介绍的模式语言描述解决方案的替代方案和权衡。我们将从简单的消息流架构开始,并随着我们解决日益复杂的需求而引入更复杂的概念,例如流程管理器。

                                                                      We tackle each of these re­quire­ments sep­ar­ately and de­scribe the solu­tion al­tern­at­ives and trade-offs using the pat­tern lan­guage in­tro­duced in this book. We will start with a simple mes­sage flow ar­chi­tec­ture and in­tro­duce more com­plex con­cepts, such as a Pro­cess Man­ager, as we ad­dress in­creas­ingly com­plex re­quire­ments.

                                                                      内部系统

                                                                      In­ternal Sys­tems

                                                                      与大多数集成场景一样,WGRUS 不是所谓的“绿地”实施,而是由各种打包和自定义应用程序组成的现有 IT 基础设施的集成。事实上,我们必须使用现有的应用程序,这常常使集成工作充满挑战。在我们的示例中,WGRUS 运行以下系统(见图)。

                                                                      As in most in­teg­ra­tion scen­arios, WGRUS is not a so-called "green field" im­ple­ment­a­tion, but rather the in­teg­ra­tion of an ex­ist­ing IT in­fra­struc­ture com­prised of a vari­ety of pack­aged and custom ap­plic­a­tions. The fact that we have to work with ex­ist­ing ap­plic­a­tions often makes in­teg­ra­tion work chal­len­ging. In our ex­ample, WGRUS runs the fol­low­ing sys­tems (see figure).

                                                                      WGRUS IT 基础设施

                                                                      WGRUS IT In­fra­struc­ture

                                                                      图形/01inf11.gif

                                                                      WGRUS 有四种不同的渠道与客户互动。客户可以访问公司网站、致电呼叫中心的客户服务代表或通过传真提交订单。客户还可以通过电子邮件接收通知。

                                                                      WGRUS has four dif­fer­ent chan­nels to in­ter­act with cus­tom­ers. Cus­tom­ers can visit the com­pany Web site, call the cus­tomer ser­vice rep­res­ent­at­ive at the call center, or submit orders via fax. Cus­tom­ers can also re­ceive no­ti­fic­a­tions via e-mail.

                                                                      WGRUS 的内部系统由会计系统(还包括计费功能)和运输系统(计算运输费用并与运输公司交互)组成。由于历史原因,WGRUS 有两个库存和目录系统。WGRUS 过去只销售小部件,但后来收购了另一家销售小部件的零售商。

                                                                      WGRUS's in­ternal sys­tems are com­prised of the ac­count­ing system, which also in­cludes billing func­tions, and the ship­ping system that com­putes ship­ping charges and in­ter­acts with the ship­ping com­pan­ies. For his­toric reas­ons, WGRUS has two in­vent­ory and cata­log sys­tems. WGRUS used to sell only wid­gets but ac­quired an­other re­tailer that sells gad­gets.

                                                                      接受订单

                                                                      Taking Orders

                                                                      我们要实现的第一个功能是接受订单。接受订单是一件好事,因为订单带来收入。然而,目前下订单是一个繁琐的手工过程,因此每个订单产生的成本很高。

                                                                      The first func­tion we want to im­ple­ment is taking orders. Taking orders is a good thing be­cause orders bring rev­enue. How­ever, pla­cing orders is cur­rently a te­di­ous manual pro­cess, so the cost in­curred with each order is high.

                                                                      简化订单处理的第一步是统一接单。客户可以通过以下三种渠道之一下订单:网站、呼叫中心或传真。不幸的是,每个系统都基于不同的技术,并以不同的数据格式存储传入的订单。呼叫中心系统是一个打包的应用程序,而网站是一个定制的J2EE应用程序。入站传真系统需要将数据手动输入到小型 Microsoft Access 应用程序中。我们希望平等对待所有订单,无论其来源如何。例如,客户应该能够通过呼叫中心下订单并在网站上检查订单状态。

                                                                      The first step to stream­lin­ing order pro­cess­ing is to unify taking orders. A cus­tomer can place orders over one of three chan­nels: Web site, call center, or fax. Un­for­tu­nately, each system is based on a dif­fer­ent tech­no­logy and stores in­com­ing orders in a dif­fer­ent data format. The call center system is a pack­aged ap­plic­a­tion, while the Web site is a custom J2EE ap­plic­a­tion. The in­bound fax system re­quires manual data entry into a small Mi­crosoft Access ap­plic­a­tion. We want to treat all orders equally, re­gard­less of their source. For ex­ample, a cus­tomer should be able to place an order via the call center and check the order status on the Web site.

                                                                      由于下订单是一个连接多个系统的异步过程,因此我们决定实施面向消息的中间件解决方案来简化订单输入流程。打包的呼叫中心应用程序在开发时并未考虑集成,因此我们必须使用通道适配器将其连接到消息传递系统。通道适配器是一个组件,可以附加到应用程序在应用程序内部发生事件时将消息发布到消息通道。使用一些通道适配器,应用程序甚至可能不知道适配器的存在。例如,数据库适配器可以向特定表添加触发器,以便应用程序每次插入一行数据时,都会向消息通道发送一条消息。 通道适配器还可以以相反的方向工作,从消息通道消费消息并触发应用程序内的操作作为响应。

                                                                      Be­cause pla­cing an order is an asyn­chron­ous pro­cess that con­nects many sys­tems, we decide to im­ple­ment a mes­sage-ori­ented mid­dle­ware solu­tion to stream­line the order entry pro­cess. The pack­aged call center ap­plic­a­tion was not de­ve­loped with in­teg­ra­tion in mind so that we have to con­nect it to the mes­saging system using a Chan­nel Ad­apter. A Chan­nel Ad­apter is a com­pon­ent that can attach to an ap­plic­a­tion and pub­lish mes­sages to a Mes­sage Chan­nel whenever an event occurs inside the ap­plic­a­tion. With some Chan­nel Ad­apters, the ap­plic­a­tion may not even be aware of the pres­ence of the ad­apter. For ex­ample, a data­base ad­apter may add trig­gers to spe­cific tables so that every time the ap­plic­a­tion in­serts a row of data, a mes­sage is sent to the Mes­sage Chan­nel. Chan­nel Ad­apters can also work in the op­pos­ite dir­ec­tion, con­sum­ing mes­sages off a Mes­sage Chan­nel and trig­ger­ing an action inside the ap­plic­a­tion in re­sponse.

                                                                      我们对入站传真应用程序使用相同的方法,将通道适配器连接到应用程序数据库。由于 Web 应用程序是自定义构建的,因此我们在应用程序内部实现消息端点代码。我们使用消息传递网关将应用程序代码与消息传递特定的代码隔离。

                                                                      We use the same ap­proach for the in­bound fax ap­plic­a­tion, con­nect­ing the Chan­nel Ad­apter to the ap­plic­a­tion data­base. Be­cause the Web ap­plic­a­tion is custom built, we im­ple­ment the Mes­sage En­d­point code inside the ap­plic­a­tion. We use a Mes­saging Gate­way to isol­ate the ap­plic­a­tion code from the mes­saging-spe­cific code.

                                                                      从三个不同渠道接受订单

                                                                      Taking Orders from Three Dif­fer­ent Chan­nels

                                                                      图形/01inf12.gif

                                                                      由于每个系统对传入订单使用不同的数据格式,因此我们使用三个消息转换器将不同的数据格式转换为遵循规范数据模型的通用新订单消息规范数据模型定义了独立于任何特定应用程序的消息格式,以便所有应用程序都可以采用这种通用格式相互通信。如果应用程序的内部格式发生变化,则只有受影响的应用程序和公共消息通道之间的消息转换器必须更改,而所有其他应用程序和消息转换器保持不受影响。用一个规范数据模型意味着我们处理两种类型的消息:规范(公共)消息和特定于应用程序的(私有)消息。应用程序特定的消息不应由该应用程序和关联的消息转换器以外的任何组件使用。 为了强化这一策略,我们以应用程序开头来命名特定于应用程序的消息通道:例如WEB_NEW_ORDER 。相反,携带规范消息的通道以消息的意图命名,没有任何前缀:例如NEW_ORDER

                                                                      Be­cause each system uses a dif­fer­ent data format for the in­com­ing orders, we use three Mes­sage Trans­lat­ors to con­vert the dif­fer­ent data formats into a common New Order mes­sage that fol­lows a Ca­non­ical Data Model. A Ca­non­ical Data Model defines mes­sage formats that are in­de­pend­ent from any spe­cific ap­plic­a­tion so that all ap­plic­a­tions can com­mu­nic­ate with each other in this common format. If the in­ternal format of an ap­plic­a­tion changes, only the Mes­sage Trans­lator between the af­fected ap­plic­a­tion and the common Mes­sage Chan­nel has to change, while all other ap­plic­a­tions and Mes­sage Trans­lat­ors remain un­af­fected. Using a Ca­non­ical Data Model means that we deal with two types of mes­sages: ca­non­ical (public) mes­sages and ap­plic­a­tion-spe­cific (private) mes­sages. Ap­plic­a­tion-spe­cific mes­sages should not be con­sumed by any com­pon­ent other than that ap­plic­a­tion and the as­so­ci­ated Mes­sage Trans­lator. To re­in­force this policy, we name ap­plic­a­tion-spe­cific Mes­sage Chan­nels start­ing with the name of the ap­plic­a­tion: for ex­ample, WEB_NEW_ORDER. In con­trast, chan­nels car­ry­ing ca­non­ical mes­sages are named after the intent of the mes­sage without any prefix: for ex­ample, NEW_ORDER.

                                                                      我们通过点对点通道将每个通道适配器连接到消息转换器,因为我们希望确保每个订单消息仅被消耗一次。如果我们将转换逻辑编程到消息传递网关中,我们就可以在不使用 Web 界面消息转换器的情况下完成任务。 然而,手动编码转换函数可能很乏味且容易出错,我们更喜欢使用一致的方法。附加的消息转换器还允许我们保护新订单流免受 Web 界面数据格式的微小变化的影响。所有消息翻译器发布到相同的NEW_ORDER 点对点通道,以便可以通过该通道处理订单,而无需考虑订单的来源。

                                                                      We con­nect each Chan­nel Ad­apter to the Mes­sage Trans­lator via a Point-to-Point Chan­nel be­cause we want to be sure that each order mes­sage is con­sumed only once. We could get away without using a Mes­sage Trans­lator for the Web in­ter­face if we pro­grammed the trans­form­a­tion logic into the Mes­saging Gate­way. How­ever, hand-coding trans­form­a­tion func­tions can be te­di­ous and error-prone, and we prefer to use a con­sist­ent ap­proach. The ad­di­tional Mes­sage Trans­lator also allows us to shield the New Order flow from minor changes in the Web in­ter­face data format. All Mes­sage Trans­lat­ors pub­lish to the same NEW_ORDER Point-to-Point Chan­nel so that orders can be pro­cessed off this chan­nel without regard to the order's origin.

                                                                      NEW_ORDER消息通道是所谓的数据类型通道,因为它仅承载一种 类型的消息:新订单。这使得消息消费者很容易知道期望的消息类型。新订单消息本身被设计为文档消息。该消息的目的不是指示接收者采取特定操作,而是将文档传递给任何感兴趣的接收者,后者可以自由决定如何处理该文档。

                                                                      The NEW_ORDER Mes­sage Chan­nel is a so-called Data­type Chan­nel be­cause it car­ries mes­sages of only one type: new orders. This makes it easy for mes­sage con­sumers to know what type of mes­sage to expect. The New Order mes­sage itself is de­signed as a Doc­u­ment Mes­sage. The intent of the mes­sage is not to in­struct the re­ceiver to take a spe­cific action, but rather to pass a doc­u­ment to any in­ter­ested re­cip­i­ent, who is free to decide how to pro­cess the doc­u­ment.

                                                                      处理订单

                                                                      Pro­cess­ing Orders

                                                                      现在我们有了独立于消息源的一致订单消息,我们准备好处理订单了。为了履行订单,我们需要完成以下步骤:

                                                                      Now that we have a con­sist­ent order mes­sage that is in­de­pend­ent from the mes­sage source, we are ready to pro­cess orders. To ful­fill an order, we need to com­plete the fol­low­ing steps:

                                                                      • 验证客户的信用状况。 如果客户有未付账单,我们要拒绝新订单。

                                                                      • Verify the cus­tomer's credit stand­ing. If the cus­tomer has out­stand­ing bills, we want to reject the new order.

                                                                      • 核实库存。 我们无法履行没有库存的商品订单。

                                                                      • Verify in­vent­ory. We can't ful­fill orders for items that are not in stock.

                                                                      • 如果客户信誉良好并且我们有库存,我们希望发货并向客户开具账单。

                                                                      • If the cus­tomer is in good stand­ing and we have in­vent­ory, we want to ship the goods and bill the cus­tomer.

                                                                      我们可以使用统一建模语言 (UML) 活动图来表达这一系列事件。活动图具有相对简单的语义,是描述包含并行活动的流程的好工具。符号非常简单;连续的活动通过简单的箭头连接起来。并行活动通过代表分叉和连接操作的粗黑条连接。分叉操作会导致所有连接的活动同时启动,而连接操作仅在所有传入活动完成后才继续。

                                                                      We can ex­press this se­quence of events using a Uni­fied Mod­el­ing Lan­guage (UML) activ­ity dia­gram. Activ­ity dia­grams have re­l­at­ively simple se­mantics and are a good tool to depict pro­cesses that in­clude par­al­lel activ­it­ies. The nota­tion is very simple; se­quen­tial activ­it­ies are con­nec­ted by simple arrows. Par­al­lel activ­it­ies are con­nec­ted by a thick black bar rep­res­ent­ing fork and join ac­tions. A fork action causes all con­nec­ted activ­it­ies to start sim­ul­tan­eously, while the join action con­tin­ues only after all in­com­ing activ­it­ies have been com­pleted.

                                                                      我们的活动图(见图)旨在并行执行“检查库存”任务和“验证客户信誉”任务。连接栏会等到两个活动都完成后才允许下一个活动开始。下一个活动验证这两个步骤的结果:我们是否有库存,客户信誉良好吗?如果两个条件都满足,则流程将继续履行订单。否则,我们将过渡到异常处理活动。例如,我们可能会打电话提醒客户支付最后一张发票或发送电子邮件让他或她知道订单将被延迟。因为本书重点关注面向消息的集成的设计方面而不是工作流建模,我们暂时将异常处理过程的细节放在一边。有关工作流架构和工作流建模的非常好的讨论,请参阅[雷曼]和[夏普]。

                                                                      Our activ­ity dia­gram (see figure) is de­signed to ex­ecute the Check In­vent­ory task and the Val­id­ate Cus­tomer Stand­ing task in par­al­lel. The join bar waits until both activ­it­ies are com­pleted before it allows the next activ­ity to start. The next activ­ity veri­fies the res­ults of both steps: Do we have in­vent­ory, and is the cus­tomer in good stand­ing? If both con­di­tions are ful­filled, the pro­cess goes on to ful­fill the order. Oth­er­wise, we trans­ition to an ex­cep­tion-hand­ling activ­ity. For ex­ample, we may call to remind the cus­tomer to pay the last in­voice or send an e-mail let­ting him or her know that the order will be delayed. Be­cause this book fo­cuses on the design as­pects of mes­sage-ori­ented in­teg­ra­tion rather than on work­flow mod­el­ing, we leave the de­tails of the ex­cep­tion-hand­ling pro­cess aside for now. For a very good dis­cus­sions of work­flow ar­chi­tec­ture and work­flow mod­el­ing, refer to [Leyman] and [Sharp].

                                                                      订单处理活动图

                                                                      Activ­ity Dia­gram for Order Pro­cess­ing

                                                                      图形/01inf13.gif

                                                                      事实证明,活动图中的活动相对较好地映射到 WGRUS IT 部门的系统。会计系统验证客户的信用状况,库存系统检查库存,运输系统启动货物的实际运输。会计系统还充当计费系统并发送发票。我们可以看到,订单的处理是一个典型的分布式业务流程的实现。

                                                                      It turns out that the activ­it­ies in the activ­ity dia­gram map re­l­at­ively nicely to the sys­tems in WGRUS's IT de­part­ment. The ac­count­ing system veri­fies the cus­tomer's credit stand­ing, the in­vent­ory sys­tems check the in­vent­ory, and the ship­ping system ini­ti­ates the phys­ical ship­ping of goods. The ac­count­ing system also acts as the billing system and sends in­voices. We can see that the pro­cess­ing of orders is a typ­ical im­ple­ment­a­tion of a dis­trib­uted busi­ness pro­cess.

                                                                      为了将逻辑活动图转换为集成设计,我们可以使用发布-订阅通道来实现 fork 操作,并使用聚合器来实现 join 操作。发布-订阅通道向所有活跃消费者发送消息;聚合接收多个传入消息并将它们组合成单个传出消息(见图)。

                                                                      To con­vert the lo­gical activ­ity dia­gram into an in­teg­ra­tion design, we can use a Pub­lish-Sub­scribe Chan­nel to im­ple­ment the fork action and an Ag­greg­ator to im­ple­ment the join action. A Pub­lish-Sub­scribe Chan­nel sends a mes­sage to all active con­sumers; an Ag­greg­ator re­ceives mul­tiple in­com­ing mes­sages and com­bines them into a single, out­go­ing mes­sage (see figure).

                                                                      使用异步消息传递的订单处理实现

                                                                      Order Pro­cess­ing Im­ple­ment­a­tion Using Asyn­chron­ous Mes­saging

                                                                      图形/01inf14.gif

                                                                      在我们的示例中,发布-订阅通道将新订单消息发送到会计系统和库存系统。聚合组合来自两个系统的结果消息并将组合的消息传递到基于内容的路由器基于内容的路由器是一个组件,它使用消息并将其未经修改地发布到基于路由器内部编码的规则选择的其他通道。基于内容的路由器相当于UML活动图中的分支。在这种情况下,如果库存检查和信用检查都是肯定的,则基于内容的路由器将消息转发到VALIDATED_ORDER频道。该通道是发布-订阅通道,因此经过验证的订单会同时到达运输和计费系统。如果客户信誉不佳或者我们手头没有库存,基于内容的路由器会将消息转发到INVALID_ORDER通道。异常处理流程(图中未显示)侦听此通道上的消息并通知客户订单被拒绝。

                                                                      In our ex­ample, the Pub­lish-Sub­scribe Chan­nel sends the New Order mes­sage to both the ac­count­ing system and the in­vent­ory system. The Ag­greg­ator com­bines the result mes­sages from both sys­tems and passes the com­bined mes­sage to a Con­tent-Based Router. A Con­tent-Based Router is a com­pon­ent that con­sumes a mes­sage and pub­lishes it, un­mod­i­fied, to a choice of other chan­nels based on rules coded inside the router. The Con­tent-Based Router is equi­val­ent to the branch in a UML activ­ity dia­gram. In this case, if both the in­vent­ory check and the credit check have been af­firm­at­ive, the Con­tent-Based Router for­wards the mes­sage to the VAL­ID­ATED_ORDER chan­nel. This chan­nel is a Pub­lish-Sub­scribe Chan­nel, so the val­id­ated order reaches both the ship­ping and the billing sys­tems. If the cus­tomer is not in good stand­ing or we have no in­vent­ory on hand, Con­tent-Based Router for­wards the mes­sage to the IN­VAL­ID_ORDER chan­nel. An ex­cep­tion-hand­ling pro­cess (not shown in the figure) listens to mes­sages on this chan­nel and no­ti­fies the cus­tomer of the re­jec­ted order.

                                                                      现在我们已经建立了整体的消息流,我们需要仔细研究一下库存函数。正如我们在需求部分中了解到的,WGRUS 有两种库存系统:一种用于小部件,一种用于小工具。因此,我们必须将库存请求路由到正确的系统。因为我们希望对其他系统隐藏库存系统的特殊性,所以我们插入另一个基于内容的路由器,该路由器根据订购的商品类型将消息路由到正确的库存系统(见图)。例如,所有商品编号以 W 开头的传入消息都会路由到小部件库存系统,所有商品编号以 G 开头的订单都会路由到小工具库存系统。

                                                                      Now that we have es­tab­lished the over­all mes­sage flow, we need to have a closer look at the in­vent­ory func­tion. As we learned in the re­quire­ments sec­tion, WGRUS has two in­vent­ory sys­tems: one for wid­gets and one for gad­gets. As a result, we have to route the re­quest for in­vent­ory to the cor­rect system. Be­cause we want to hide the pe­cu­li­ar­it­ies of the in­vent­ory sys­tems from the other sys­tems, we insert an­other Con­tent-Based Router that routes the mes­sage to the cor­rect in­vent­ory system based on the type of item ordered (see figure). For ex­ample, all in­com­ing mes­sages with an item number start­ing with W are routed to the widget in­vent­ory system, and all orders with an item number start­ing with G are routed to the gadget in­vent­ory system.

                                                                      路由库存请求

                                                                      Rout­ing the In­vent­ory Re­quest

                                                                      图形/01inf15.gif

                                                                      请注意,基于内容的路由器和库存系统之间的点对点通道上的消息意图与之前的通道不同。这些通道包含命令消息,指示系统执行指定命令的消息,在本例中验证项目的库存。

                                                                      Note that the intent of mes­sages on the Point-to-Point Chan­nels between the Con­tent-Based Router and the in­vent­ory sys­tems is dif­fer­ent from the pre­vi­ous chan­nel. These chan­nels con­tain Com­mand Mes­sages, mes­sages that in­struct the system to ex­ecute the spe­cified com­mand, in this case veri­fy­ing the in­vent­ory of an item.

                                                                      由于小部件库存系统和小工具库存系统使用不同的内部数据格式,因此我们再次插入消息转换器以将规范的新订单消息格式转换为系统特定的格式。

                                                                      Be­cause the widget in­vent­ory system and the gadget in­vent­ory system use dif­fer­ent in­ternal data formats, we again insert Mes­sage Trans­lat­ors to con­vert from the ca­non­ical New Order mes­sage format to a system-spe­cific format.

                                                                      如果订单商品既不以 W 也不以 G 开头,会发生什么情况?基于内容的路由器将消息路由到INVALID_ORDER通道,以便可以通过通知客户等方式相应地处理无效订单。该通道是无效消息通道的典型示例。 它强调了这样一个事实:消息的含义会根据其所在的渠道而变化。NEW_ORDER通道和INVALID_ORDER通道都传输相同类型的消息,但在一种情况下正在处理新订单,而在另一种情况下订单被视为无效。

                                                                      What hap­pens if the order item starts neither with W nor G? The Con­tent-Based Router routes the mes­sage to the IN­VAL­ID_ORDER chan­nel so that the in­valid order can be pro­cessed ac­cord­ingly, by no­ti­fy­ing the cus­tomer, for ex­ample. This chan­nel is a typ­ical ex­ample of an In­valid Mes­sage Chan­nel. It high­lights the fact that the mean­ing of a mes­sage changes de­pend­ing on what chan­nel it is on. Both the NEW_ORDER chan­nel and the IN­VAL­ID_ORDER chan­nel trans­port the same type of mes­sage, but in one case a new order is being pro­cessed, while in the other case the order is deemed in­valid.

                                                                      到目前为止,我们假设每个订单只包含一个商品。这对我们的客户来说非常不方便,因为他们必须为每件商品下新订单。此外,我们最终会向同一客户运送多个订单,并产生不必要的运输费用。但是,如果我们允许一个订单中包含多个商品,那么哪个库存系统应该验证该订单的库存?我们可以使用发布-订阅通道将订单发送到每个库存系统,以挑选它可以处理的项目。但是无效的物品会怎样呢?我们如何注意到库存系统都没有处理该物品?我们想要维护基于内容的路由器的中央控制给了我们,但我们需要能够单独路由每个订单项目。

                                                                      So far, we have as­sumed that each order con­tains only a single item. This would be pretty in­con­veni­ent for our cus­tom­ers be­cause they would have to place a new order for each item. Also, we would end up ship­ping mul­tiple orders to the same cus­tomer and incur un­ne­ces­sary ship­ping costs. How­ever, if we allow mul­tiple items inside an order, which in­vent­ory system should verify the in­vent­ory for this order? We could use a Pub­lish-Sub­scribe Chan­nel to send the order to each in­vent­ory system to pick out the items that it can pro­cess. But what would then happen to in­valid items? How would we notice that neither in­vent­ory system pro­cessed the item? We want to main­tain the cent­ral con­trol the Con­tent-Based Router gives us, but we need to be able to route each order item in­di­vidu­ally.

                                                                      因此,我们插入一个Splitter ,该组件将单个消息分成多个单独的消息。在我们的例子中,拆分器将单个订单消息拆分为多个订单项目消息。然后,可以像以前一样使用基于内容的路由器将每个订单项目消息路由到正确的库存系统;见下图。

                                                                      There­fore, we insert a Split­ter, a com­pon­ent that breaks a single mes­sage into mul­tiple in­di­vidual mes­sages. In our case, the Split­ter splits a single Order mes­sage into mul­tiple Order Item mes­sages. Each Order Item mes­sage can then be routed to the cor­rect in­vent­ory system using a Con­tent-Based Router as before; see the fol­low­ing figure.

                                                                      单独处理订单项目

                                                                      Pro­cess­ing Order Items In­di­vidu­ally

                                                                      图形/01inf16.gif

                                                                      当然,当所有物品的库存都得到验证后,我们需要将消息重新组合成一条消息。我们已经了解到,可以将多个消息组合成单个消息的组件是聚合器。使用 Splitter和Aggregator ,我们可以在逻辑上将单个订单项的消息流与完整订单的消息流分开。

                                                                      Nat­ur­ally, when the in­vent­ory for all items has been veri­fied, we need to re­com­bine the mes­sages into a single mes­sage. We already learned that the com­pon­ent that can com­bine mul­tiple mes­sages into a single mes­sage is the Ag­greg­ator. Using both a Split­ter and an Ag­greg­ator, we can lo­gic­ally sep­ar­ate the mes­sage flow for in­di­vidual order items from the flow for a com­plete order.

                                                                      在设计聚合器时,我们必须做出三个关键决定:

                                                                      When design­ing an Ag­greg­ator, we have to make three key de­cisions:

                                                                      • 哪些消息属于同一组(相关性)?

                                                                      • Which mes­sages belong to­gether (cor­rel­a­tion)?

                                                                      • 我们如何确定所有消息都已收到(完整性条件)?

                                                                      • How do we de­term­ine that all mes­sages are re­ceived (the com­plete­ness con­di­tion)?

                                                                      • 我们如何将各个消息组合成一个结果消息(聚合算法)?

                                                                      • How do we com­bine the in­di­vidual mes­sages into one result mes­sage (the ag­greg­a­tion al­gorithm)?

                                                                      让我们一一解决这些问题。我们无法通过客户 ID 关联订单项目,因为客户可能会在短时间内连续下多个订单。因此,我们需要为每个订单分配一个唯一的订单ID。我们通过将内容丰富器插入到接受订单的解决方案部分来实现此目的(见图)。内容丰富器是一个将缺失的数据项添加到传入消息中的组件。在我们的例子中,内容丰富器向消息添加了唯一的订单 ID。

                                                                      Let's tackle these issues one by one. We can't cor­rel­ate order items by the cus­tomer ID, be­cause a cus­tomer may place mul­tiple orders in short suc­ces­sion. There­fore, we need a unique order ID for each order. We ac­com­plish this by in­sert­ing a Con­tent En­richer into the part of the solu­tion that takes orders (see figure). A Con­tent En­richer is a com­pon­ent that adds miss­ing data items to an in­com­ing mes­sage. In our case, the Con­tent En­richer adds a unique order ID to the mes­sage.

                                                                      通过 Enricher 接受订单

                                                                      Taking Orders with En­richer

                                                                      图形/01inf17.gif

                                                                      现在我们有了一个订单 ID 来关联订单项消息,我们需要定义订单项聚合器的完整性条件和聚合算法。因为我们将所有消息(包括无效商品)路由到聚合器,所以聚合器可以简单地使用订单中的商品数量(订单消息中的字段之一)进行计数,直到所有订单商品到达为止。聚合算法同样简单。聚合将所有订单项消息连接回单个订单消息并将其发布到VALIDATED_ORDER 通道

                                                                      Now that we have an order ID to cor­rel­ate Order Item mes­sages, we need to define the com­plete­ness con­di­tion and the ag­greg­a­tion al­gorithm for the Order Item Ag­greg­ator. Be­cause we route all mes­sages, in­clud­ing in­valid items, to the Ag­greg­ator, the Ag­greg­ator can simply use the number of items in the order (one of the fields in the Order mes­sage) to count until all order items arrive. The ag­greg­a­tion al­gorithm is sim­il­arly simple. The Ag­greg­ator con­cat­en­ates all Order Item mes­sages back into a single Order mes­sage and pub­lishes it to the VAL­ID­ATED_ORDER chan­nel.

                                                                      Splitter、Message Router和Aggregator的组合相当常见。我们将其称为组合消息处理器为了简化该图,我们将组合消息处理器的符号插入到原始消息流图中:

                                                                      The com­bin­a­tion of a Split­ter, a Mes­sage Router, and an Ag­greg­ator is fairly common. We refer to it as a Com­posed Mes­sage Pro­cessor. To sim­plify the figure, we insert the symbol for a Com­posed Mes­sage Pro­cessor into the ori­ginal mes­sage flow dia­gram:

                                                                      修订后的订单流程实施

                                                                      Re­vised Order Pro­cess Im­ple­ment­a­tion

                                                                      图形/01inf18.gif

                                                                      检查状态

                                                                      Check­ing Status

                                                                      尽管通过消息通道连接系统,但履行订单可能需要一些时间。例如,我们可能缺少某种商品,并且库存系统可能会保留库存检查消息,直到新商品到达为止。这是异步消息传递的优点之一:通信被设计为按照组件的速度进行。当库存系统保留该消息时,会计系统仍然可以验证客户的信用状况。完成这两个步骤后,聚合器将发布“已验证订单”消息以启动发货和开具发票。

                                                                      Des­pite con­nect­ing the sys­tems via Mes­sage Chan­nels, ful­filling an order can take some amount of time. For ex­ample, we may be out of a cer­tain item and the in­vent­ory system may be hold­ing the In­vent­ory Check mes­sage until new items arrive. This is one of the ad­vant­ages of asyn­chron­ous mes­saging: the com­mu­nic­a­tion is de­signed to happen at the pace of the com­pon­ents. While the in­vent­ory system is hold­ing the mes­sage, the ac­count­ing system can still verify the cus­tomer's credit stand­ing. Once both steps are com­pleted, the Ag­greg­ator pub­lishes the Val­id­ated Order mes­sage to ini­ti­ate ship­ment and in­voicing.

                                                                      长期运行的业务流程还意味着客户和经理都可能想知道特定订单的状态。例如,如果某些商品缺货,客户可能决定仅处理有库存的商品。或者,如果客户还没有收到货物,我们可以告诉他或她货物正在运送途中(包括运输公司的追踪号码)或仓库内部出现延误,这会很有用。

                                                                      A long-run­ning busi­ness pro­cess also means that both cus­tom­ers and man­agers are likely to want to know the status of a spe­cific order. For ex­ample, if cer­tain items are out of in­vent­ory, the cus­tomer may decide to pro­cess just those items that are in stock. Or, if the cus­tomer has not re­ceived the goods, it is useful if we can tell him or her that the goods are on their way (in­clud­ing the ship­ping com­pany's track­ing number) or that there is an in­ternal delay in the ware­house.

                                                                      使用当前设计跟踪订单状态并不那么容易。相关消息流经多个系统。为了确定步骤序列中订单的状态,我们必须知道与该订单相关的“最后”消息。发布-订阅通道的优点之一是我们可以在不干扰消息流的情况下添加其他订阅者。我们可以使用此属性来侦听新的和经过验证的订单并将它们存储在Message Store中。然后,我们可以查询消息存储数据库以获取订单状态(见图):

                                                                      Track­ing the status of an order with the cur­rent design is not so easy. Re­lated mes­sages flow through mul­tiple sys­tems. To as­cer­tain the status of the order in the se­quence of steps, we would have to know the "last" mes­sage re­lated to this order. One of the ad­vant­ages of a Pub­lish-Sub­scribe Chan­nel is that we can add ad­di­tional sub­scribers without dis­turb­ing the flow of mes­sages. We can use this prop­erty to listen in to new and val­id­ated orders and store them in a Mes­sage Store. We could then query the Mes­sage Store data­base for the status of an order (see figure):

                                                                      添加消息存储以跟踪订单状态

                                                                      Adding a Mes­sage Store to Track Order Status

                                                                      图形/01inf19.gif

                                                                      在我们使用点对点通道的情况下,我们不能简单地将订阅者添加到通道中,因为点对点通道确保每条消息仅由单个订阅者消费。但是,我们可以插入一个Wire Tap,这是一个简单的组件,它使用一个通道的消息并将其发布到两个通道。然后我们可以使用第二个通道将消息发送到消息存储;见图。

                                                                      In situ­ations where we use a Point-Point Chan­nel, we cannot simply add a sub­scriber to the chan­nel, be­cause a Point-to-Point Chan­nel en­sures that each mes­sage is only con­sumed by a single sub­scriber. How­ever, we can insert a Wire Tap, a simple com­pon­ent that con­sumes a mes­sage off one chan­nel and pub­lishes it to two chan­nels. We can then use the second chan­nel to send mes­sages to the Mes­sage Store; see figure.

                                                                      使用窃听器跟踪消息

                                                                      Track­ing Mes­sages with a Wire Tap

                                                                      图形/01inf20.gif

                                                                      将消息数据存储在中央数据库中还有另一个显着的优点。在最初的设计中,每条消息都必须携带无关的数据,以便继续处理该消息。例如,验证客户信誉消息可能必须传输各种客户数据,即使它只需要客户 ID。此附加数据是必要的,以便生成的消息仍然包含原始订单消息中的所有数据。在消息流开始时将 New Order 消息存储在Message Store中的优点是所有后续组件都可以引用Message Store用于重要的消息数据,而无需所有中间步骤携带数据。我们将这样的功能称为“索赔检查消息”,可以“签入”数据以供以后检索。这种方法的缺点是访问中央数据存储不如通过异步消息通道发送消息可靠

                                                                      Stor­ing mes­sage data in a cent­ral data­base has an­other sig­ni­fic­ant ad­vant­age. In the ori­ginal design, each mes­sage had to carry ex­traneous data in order to con­tinue pro­cess­ing the mes­sage down the line. For ex­ample, the Val­id­ate Cus­tomer Stand­ing mes­sage may have had to trans­port all sorts of cus­tomer data even though it re­quired only the cus­tomer ID. This ad­di­tional data is ne­ces­sary so that the res­ult­ing mes­sage still con­tains all data from the ori­ginal order mes­sage. Stor­ing the New Order mes­sage in a Mes­sage Store at the be­gin­ning of the mes­sage flow has the ad­vant­age that all sub­se­quent com­pon­ents can refer to the Mes­sage Store for im­port­ant mes­sage data without all in­ter­me­di­ate steps having to carry the data along. We refer to such a func­tion as Claim Check mes­sages can "check in" data for later re­trieval. The down­side of this ap­proach is that ac­cess­ing a cent­ral data store is not as re­li­able as send­ing mes­sages across asyn­chron­ous Mes­sage Chan­nels.

                                                                      现在,消息存储负责维护与新消息相关的数据以及消息在流程中的进度。这些数据为我们提供了足够的信息,可以使用消息存储来确定流程中接下来所需的步骤,而不是使用固定的消息通道连接组件。例如,如果数据库包含来自库存系统和计费系统的回复消息,我们可以得出结论,订单已经过验证,并且可以向运输和计费系统发送消息。我们可以在消息存储中直接完成此决定,而不是在单独的聚合器组件中做出此决定。实际上,我们正在将消息存储转变为流程管理器

                                                                      Now, the Mes­sage Store is re­spons­ible for main­tain­ing data re­lated to the new mes­sage as well as the pro­gress of the mes­sage within the pro­cess. This data gives us enough in­form­a­tion to use the Mes­sage Store to de­term­ine the next re­quired steps in the pro­cess rather than con­nect­ing com­pon­ents with fixed Mes­sage Chan­nels. For ex­ample, if the data­base con­tains reply mes­sages from both the in­vent­ory sys­tems and the billing system, we can con­clude that the order has been val­id­ated and can send a mes­sage to the ship­ping and billing sys­tems. In­stead of making this de­cision in a sep­ar­ate Ag­greg­ator com­pon­ent, we can do it right in the Mes­sage Store. Ef­fect­ively, we are turn­ing the Mes­sage Store into a Pro­cess Man­ager.

                                                                      流程管理器管理系统中消息流的核心组件。流程管理器提供两个主要功能:

                                                                      A Pro­cess Man­ager is a cent­ral com­pon­ent that man­ages the flow of mes­sages through the system. The Pro­cess Man­ager provides two main func­tions:

                                                                      • 在消息之间存储数据(在“流程实例”内)

                                                                      • Stor­ing data between mes­sages (inside a "pro­cess in­stance")

                                                                      • 跟踪进度并确定下一步(通过使用“流程模板”)

                                                                      • Keep­ing track of pro­gress and de­term­in­ing the next step (by using a "pro­cess tem­plate")

                                                                      使用流程管理器处理订单

                                                                      Pro­cess­ing Orders with a Pro­cess Man­ager

                                                                      图形/01inf21.gif

                                                                      这种架构将各个系统(例如,库存系统)转变为共享业务功能,其他组件可以将其作为服务进行访问,从而提高重用性并简化维护。这些服务可以通过消息流连接在一起(例如,使用组合消息处理器检查每个订单项的库存状态)或通过流程。使用流程管理器使得更改消息流比我们以前的方法容易得多。

                                                                      This ar­chi­tec­ture turns the in­di­vidual sys­tems (e.g., the in­vent­ory sys­tems) into shared busi­ness func­tions that can be ac­cessed by other com­pon­ents as ser­vices, thus in­creas­ing reuse and sim­pli­fy­ing main­ten­ance. The ser­vices can be wired to­gether via a mes­sage flow (for ex­ample, using a Com­posed Mes­sage Pro­cessor to check in­vent­ory status for each order item) or or­ches­trated via a Pro­cess Man­ager. Using a Pro­cess Man­ager makes chan­ging the flow of mes­sages much easier than our pre­vi­ous ap­proach.

                                                                      新的架构将所有服务公开给公共服务总线,以便可以从任何其他组件调用它们。我们可以通过添加从中央服务注册表查找(“发现”)服务的设施,将 WGRUS IT 基础架构转变为 SOA。为了参与此 SOA,每个服务都必须提供附加功能。例如,每个服务都必须公开一个描述该服务提供的功能的接口契约。每个请求-答复服务还需要支持返回地址的概念。 退货地址允许调用者(服务使用者)指定服务应发送回复消息的通道。这对于允许在不同上下文中重用服务非常重要,每个上下文可能需要自己的回复消息通道。

                                                                      The new ar­chi­tec­ture ex­poses all ser­vices to a common ser­vices bus so that they can be in­voked from any other com­pon­ent. We could turn the WGRUS IT in­fra­struc­ture into an SOA by adding fa­cil­it­ies to look up ("dis­cover") a ser­vice from a cent­ral ser­vice re­gistry. In order to par­ti­cip­ate in this SOA, each ser­vice would have to provide ad­di­tional func­tions. For ex­ample, each ser­vice would have to expose an in­ter­face con­tract that de­scribes the func­tions provided by the ser­vice. Each re­quest-reply ser­vice also needs to sup­port the concept of a Return Ad­dress. A Return Ad­dress allows the caller (the ser­vice con­sumer) to spe­cify the chan­nel where the ser­vice should send the reply mes­sage. This is im­port­ant to allow the ser­vice to be reused in dif­fer­ent con­texts, each of which may re­quire its own chan­nel for reply mes­sages.

                                                                      流程管理器本身使用持久存储(通常是文件或关系数据库)来存储与每个流程实例关联的数据。为了允许 Web 界面查询订单的状态,我们可以向流程管理器或订单数据库发送一条消息。然而,检查状态是一个同步过程,客户希望立即得到响应。由于Web界面是自定义应用程序,因此我们决定直接访问订单数据库来查询订单状态。这种形式的共享数据库是最简单、最有效的方法,并且我们始终确保Web界面显示最新的状态。这种方法的潜在缺点是 Web 界面与数据库紧密耦合,这是我们愿意采取的权衡。

                                                                      The Pro­cess Man­ager itself uses a per­sist­ent store (typ­ic­ally files or a re­la­tional data­base) to store data as­so­ci­ated with each pro­cess in­stance. To allow the Web in­ter­face to query the status of an order, we could send a mes­sage to the Pro­cess Man­ager or the order data­base. How­ever, check­ing status is a syn­chron­ous pro­cessthe cus­tomer ex­pects the re­sponse right away. Be­cause the Web in­ter­face is a custom ap­plic­a­tion, we decide to access the order data­base dir­ectly to query the order status. This form of Shared Data­base is the simplest and most ef­fi­cient ap­proach, and we are always en­sured that the Web in­ter­face dis­plays the most cur­rent status. The po­ten­tial down­side of this ap­proach is that the Web in­ter­face is tightly coupled to the data­base, a trade-off that we are will­ing to take.

                                                                      将系统公开为服务的一个困难是由于许多遗留系统在构建时没有考虑到返回地址等功能。因此,我们用智能代理“包装”对遗留系统的访问。该智能代理通过附加功能增强了基本系统服务,使其能够参与 SOA。为此,智能代理拦截来自基本服务的请求和回复消息(见图)。

                                                                      One dif­fi­culty in ex­pos­ing sys­tems as ser­vices res­ults from the fact that many legacy sys­tems were not built with fea­tures such as Return Ad­dress in mind. There­fore, we "wrap" access to the legacy system with a Smart Proxy. This Smart Proxy en­hances the basic system ser­vice with ad­di­tional cap­ab­il­ity so that it can par­ti­cip­ate in an SOA. To do this, the Smart Proxy in­ter­cepts both re­quest and reply mes­sages to and from the basic ser­vice (see figure).

                                                                      插入智能代理将遗留系统转变为共享服务

                                                                      In­sert­ing a Smart Proxy to Turn a Legacy System into a Shared Ser­vice

                                                                      图形/01inf22.gif

                                                                      智能代理可以存储来自请求消息的信息(例如,请求者指定的返回地址)并使用该信息来处理回复消息(例如,将其路由到正确的回复通道)。智能代理对于跟踪外部服务的服务质量(例如响应时间)也非常有用。

                                                                      The Smart Proxy can store in­form­a­tion from the re­quest mes­sage (e.g., the Return Ad­dress spe­cified by the re­questor) and use this in­form­a­tion to pro­cess the reply mes­sage, (e.g., route it to the cor­rect reply chan­nel). A Smart Proxy is also very useful to track qual­ity of ser­vice (e.g., re­sponse times) of an ex­ternal ser­vice.

                                                                      更换地址

                                                                      Change Ad­dress

                                                                      WGRUS 需要处理多个地址。例如,发票必须发送到客户的帐单地址,而货物则运送到送货地址。我们希望允许客户通过Web界面维护所有这些地址,以消除不必要的手动步骤。

                                                                      WGRUS needs to deal with a number of ad­dresses. For ex­ample, the in­voice has to be sent to the cus­tomer's billing ad­dress, while the goods are shipped to the ship­ping ad­dress. We want to allow the cus­tomer to main­tain all these ad­dresses through the Web in­ter­face to elim­in­ate un­ne­ces­sary manual steps.

                                                                      我们可以选择两种基本方法来获取正确的账单和送货地址到账单和送货系统:

                                                                      We can choose between two basic ap­proaches to get the cor­rect billing and ship­ping ad­dresses to the billing and ship­ping sys­tems:

                                                                      • 每条新订单消息中都包含地址数据

                                                                      • In­clude ad­dress data with every New Order mes­sage

                                                                      • 在每个系统中存储地址数据并复制更改

                                                                      • Store ad­dress data in each system and rep­lic­ate changes

                                                                      第一个选项的优点是我们可以使用现有的集成通道来传输附加信息。一个潜在的缺点是流经中间件基础设施的额外数据;即使地址更改的频率可能要低得多,我们也会将地址数据与每个订单一起传递。

                                                                      The first option has the ad­vant­age that we can use an ex­ist­ing in­teg­ra­tion chan­nel to trans­port the ad­di­tional in­form­a­tion. A po­ten­tial down­side is the ad­di­tional data flow­ing across the mid­dle­ware in­fra­struc­ture; we pass the ad­dress data along with every order even though the ad­dress may change much less fre­quently.

                                                                      在实施第一个选项时,我们需要考虑到计费和运输系统是打包的应用程序,并且在设计时可能没有考虑到集成。因此,他们不太可能接受新订单的地址,而是使用存储在本地数据库中的地址。为了使系统能够使用新订单消息更新地址,我们需要在计费系统(和运输系统)中执行两个功能:首先,我们必须更新地址,然后我们必须发送账单(或运送商品)。由于两条消息的顺序很重要,因此我们插入一个简单的流程管理器接收新订单消息的组件(其中包括当前的送货和帐单地址,并向帐单(或送货)系统发布两条单独的消息(见图)。

                                                                      When im­ple­ment­ing the first option, we need to con­sider that the billing and ship­ping sys­tems are pack­aged ap­plic­a­tions and were likely not de­signed with in­teg­ra­tion in mind. As such, they are un­likely to be able to accept ad­dresses with a new order but rather use the ad­dress that is stored in their local data­base. To enable the sys­tems to update the ad­dress with the New Order mes­sage, we need to ex­ecute two func­tions in the billing system (and the ship­ping system): First, we must update the ad­dress, and then we must send the bill (or ship the goods). Be­cause the order of the two mes­sages mat­ters, we insert a simple Pro­cess Man­ager com­pon­ent that re­ceives a New Order mes­sage (which in­cludes the cur­rent ship­ping and billing ad­dresses and pub­lishes two sep­ar­ate mes­sages to the billing (or ship­ping) system (see figure).

                                                                      在新订单消息中包含地址数据

                                                                      In­clud­ing Ad­dress Data in the New Order Mes­sage

                                                                      图形/01inf23.gif

                                                                      我们需要记住,通道适配器要求消息采用应用程序使用的专有格式(使用所谓的私有消息)。由于新订单消息以规范消息格式到达,因此我们需要在两种格式之间执行转换。我们可以将转换构建到Process Manager 中,但实际上我们更喜欢外部消息转换器,以便Process不会受到应用程序所需的可能复杂的数据格式的影响。

                                                                      We need to keep in mind that the Chan­nel Ad­apters re­quire mes­sages to be format­ted in the pro­pri­et­ary formats used by the ap­plic­a­tions (using so-called private mes­sages). Be­cause the New Order mes­sage ar­rives in the ca­non­ical mes­sage format, we need to per­form a trans­la­tion between the two formats. We could build the trans­form­a­tion into the Pro­cess Man­ager, but we ac­tu­ally prefer ex­ternal Mes­sage Trans­lat­ors so that the logic inside the Pro­cess Man­ager is not af­fected by the pos­sibly com­plic­ated data format re­quired by the ap­plic­a­tions.

                                                                      第二个选项使用数据复制将地址更改传播到所有受影响的系统,独立于新订单流程。每当 Web 界面中的地址信息发生更改时,我们都会使用发布-订阅通道将更改传播到所有感兴趣的系统。 系统在内部存储更新后的地址,并在订单消息到达时使用它。这种方法减少了消息流量(假设客户更改地址的频率低于下订单的频率)。它还可以减少系统之间的耦合。任何使用地址的系统都可以订阅ADDRESS_CHANGE通道,而不会影响任何其他系统。

                                                                      The second option uses data rep­lic­a­tion to propag­ate ad­dress changes to all af­fected sys­tems in­de­pend­ently of the New Order pro­cess. Whenever the ad­dress in­form­a­tion changes in the Web in­ter­face, we propag­ate the changes to all in­ter­ested sys­tems using a Pub­lish-Sub­scribe Chan­nel. The sys­tems store the up­dated ad­dress in­tern­ally and use it when an Order mes­sage ar­rives. This ap­proach re­duces mes­sage traffic (as­sum­ing cus­tom­ers change ad­dresses less fre­quently than they place orders). It can also reduce coup­ling between sys­tems. Any system that uses an ad­dress can sub­scribe to the AD­DRESS_CHANGE chan­nel without af­fect­ing any other sys­tems.

                                                                      由于我们正在处理多种类型的地址(送货地址和帐单地址),因此我们需要确保每个系统中仅存储正确类型的地址。如果地址是帐单地址,我们需要避免向运输系统发送地址更改消息。我们通过使用消息过滤器来实现这一点,该器仅传递符合特定条件的消息(见图)。

                                                                      Be­cause we are deal­ing with mul­tiple types of ad­dresses (ship­ping and billing ad­dresses), we need to make sure that only the right type of ad­dress is stored in each system. We need to avoid send­ing an ad­dress change mes­sage to the ship­ping system if the ad­dress is a billing ad­dress. We ac­com­plish this by using Mes­sage Fil­ters that pass only mes­sages match­ing cer­tain cri­teria (see figure).

                                                                      我们还使用消息转换器将通用地址更改消息转换为应用程序使用的特定消息格式。在这种情况下,我们不必为Web 界面使用消息转换器,因为我们将规范数据模型定义为与 Web 界面应用程序的格式相同。如果我们想在未来引入其他更改地址的方式,这可能会限制我们的灵活性,但目前已经足够了。

                                                                      We also use Mes­sage Trans­lat­ors to trans­late the gen­eric Ad­dress Change mes­sage into the spe­cific mes­sage format used by the ap­plic­a­tions. In this case, we do not have to use a Mes­sage Trans­lator for the Web in­ter­face be­cause we define the Ca­non­ical Data Model as equal to the format of the Web in­ter­face ap­plic­a­tion. This could limit our flex­ib­il­ity if we want to in­tro­duce other ways of chan­ging ad­dresses in the future, but for now it is suf­fi­cient.

                                                                      通过单独的发布-订阅通道传播地址更改

                                                                      Propagat­ing Ad­dress Changes via a Sep­ar­ate Pub­lish-Sub­scribe Chan­nel

                                                                      图形/01inf24.gif

                                                                      运输和计费系统都将地址存储在关系数据库中,因此我们使用数据库通道适配器来更新每个系统中的数据。

                                                                      Both the ship­ping and the billing sys­tems store ad­dresses in a re­la­tional data­base, so we use a data­base Chan­nel Ad­apter to update the data in each system.

                                                                      我们如何在这两个选项之间做出决定?在我们的情况下,消息流量并不是什么大问题,因为我们每天只处理几百个订单,因此任何一种解决方案都相当有效。主要的决策驱动因素将是应用程序的内部结构。我们可能无法将地址直接插入数据库,而是通过应用程序的业务层。在这种情况下,应用程序可以执行额外的验证步骤并记录地址更改活动。系统甚至可以编程为每次地址更改时通过电子邮件向客户发送确认消息。如果每个订单都进行更新,这会变得非常烦人。

                                                                      How do we decide between the two op­tions? In our situ­ation, the mes­sage traffic is not much of a con­cern be­cause we pro­cess only a few hun­dred orders a day, so either solu­tion would be reas­on­ably ef­fi­cient. The main de­cision driver is going to be the in­ternal struc­ture of the ap­plic­a­tions. We may not be able to insert the ad­dresses dir­ectly into the data­base, but rather through the ap­plic­a­tions' busi­ness layer. In this case, the ap­plic­a­tions may per­form ad­di­tional val­id­a­tion steps and record the ad­dress change activ­ity. The system may even be pro­grammed to e-mail a con­firm­a­tion mes­sage to the cus­tomer every time the ad­dress changes. This would get very an­noy­ing if the update oc­curred with every order. Such a con­di­tion would favor propagat­ing ad­dress changes using ded­ic­ated mes­sages that are sent only when the cus­tomer ac­tu­ally changes the ad­dress.

                                                                      一般来说,我们更喜欢定义明确、独立的业务操作,例如“更改地址”和“下订单”,因为它们为我们编排业务流程提供了更大的灵活性。这一切都归结为粒度和相关权衡的问题。由于进行过多的远程调用或发送消息,细粒度的接口可能会导致系统运行缓慢。例如,想象一个接口公开一个单独的方法来更改每个地址字段。如果通信发生在单个应用程序内(仅更新那些已更改的字段),则此方法将非常有效。在集成场景中,发送六到七条消息来更新地址将是一笔巨大的开销,另外,我们还必须处理同步各个消息的问题。细粒度的接口也会导致紧密的耦合。如果我们通过添加新字段来更改地址格式,则必须定义新的消息格式并更改所有其他应用程序以发送附加消息。

                                                                      In gen­eral, we prefer well-defined, self-con­tained busi­ness ac­tions such as "Change Ad­dress" and "Place Order" be­cause they give us more flex­ib­il­ity in or­ches­trat­ing the busi­nesses pro­cesses. It all comes down to a ques­tion of gran­u­lar­ity and the as­so­ci­ated trade-offs. Fine-grained in­ter­faces can lead to slug­gish sys­tems due to an ex­cess­ive number of remote calls being made or mes­sages being sent. For ex­ample, ima­gine an in­ter­face that ex­poses a sep­ar­ate method to change each ad­dress field. This ap­proach would be ef­fi­cient if the com­mu­nic­a­tion hap­pens inside a single ap­plic­a­tionyou update only those fields that changed. In an in­teg­ra­tion scen­ario, send­ing six or seven mes­sages to update an ad­dress would be a sig­ni­fic­ant over­head, plus we would have to deal with syn­chron­iz­ing the in­di­vidual mes­sages. Fine-grained in­ter­faces also lead to tight coup­ling. If we change the ad­dress format by adding a new field, we have to define new mes­sage formats and change all other ap­plic­a­tions to send an ad­di­tional mes­sage.

                                                                      粗粒度接口可以解决这些问题,但需要付出一定的代价。我们发送的消息较少,因此效率更高且耦合程度较低。然而,过于粗糙的接口会限制我们的灵活性。如果“发送发票”和“更改地址”合并为一个外部函数,那么我们将永远无法在不发送帐单的情况下更改地址。因此,一如既往,最好的答案是折中方案,并且取决于现实生活场景中的具体权衡。

                                                                      Coarse-grained in­ter­faces solve these issues, but at a cost. We send fewer mes­sages and are there­fore more ef­fi­cient and less tightly coupled. How­ever, in­ter­faces that are too coarse can limit our flex­ib­il­ity. If Send In­voice and Change Ad­dress are com­bined into one ex­ternal func­tion, we will never be able to change an ad­dress without send­ing a bill. So, as always, the best answer is the happy medium and de­pends on the spe­cific trade-offs at work in the real-life scen­ario.

                                                                      新目录

                                                                      New Cata­log

                                                                      要下订单,客户需要在线查看当前提供的商品及其价格。WGRUS 的目录由各个供应商的产品驱动。然而,WGRUS 向客户提供的服务之一是允许他们在同一网站上查看小部件和小工具,并在一个订单中订购两种类型的商品。此功能是信息门户场景的示例,我们将多个源的信息组合到一个视图中。

                                                                      To place orders, cus­tom­ers need to see the cur­rently offered items and their prices online. WGRUS's cata­log is driven by the of­fer­ings from the re­spect­ive sup­pli­ers. How­ever, one of the ser­vices that WGRUS provides to its cus­tom­ers is al­low­ing them to view wid­gets and gad­gets on the same site and to order both types of items in a single order. This func­tion is an ex­ample of an In­form­a­tion Portal scen­ari­owe com­bine in­form­a­tion from mul­tiple sources into a single view.

                                                                      事实证明,两家供应商每三个月更新一次产品目录。因此,创建实时消息传递基础设施来将目录更改从供应商传播到 WGRUS 的意义相对较小。相反,我们使用文件传输集成将目录数据从供应商移动到 WGRUS。使用文件的另一个优点是可以使用 FTP 或类似协议在公共网络上轻松高效地传输文件。相比之下,大多数异步消息传递基础设施在公共互联网上无法正常工作。

                                                                      It turns out that both sup­pli­ers update their product cata­log once every three months. There­fore, it makes re­l­at­ively little sense to create a real-time mes­saging in­fra­struc­ture to propag­ate cata­log changes from the sup­pli­ers to WGRUS. In­stead, we use File Trans­fer in­teg­ra­tion to move cata­log data from sup­pli­ers to WGRUS. The other ad­vant­age of using files is that they are easily and ef­fi­ciently trans­por­ted across public net­works using FTP or sim­ilar pro­to­cols. In com­par­ison, most asyn­chron­ous mes­saging in­fra­struc­tures do not work well over the public In­ter­net.

                                                                      我们仍然可以使用转换器和适配器将数据转换为我们的内部目录格式。然而,这些翻译人员一次处理整个目录,而不是一次处理一个项目。如果我们处理相同格式的大量数据,这种方法会更有效。

                                                                      We still can use trans­lat­ors and ad­apters to trans­form the data to our in­ternal cata­log format. How­ever, these trans­lat­ors pro­cess a whole cata­log at once in­stead of one item at a time. This ap­proach is much more ef­fi­cient if we are deal­ing with large amounts of data in the same format.

                                                                      通过文件传输更新目录数据

                                                                      Up­dat­ing Cata­log Data via File Trans­fer

                                                                      图形/01inf25.gif

                                                                      公告

                                                                      An­nounce­ments

                                                                      为了改善业务,我们希望每隔一段时间就向客户宣布特价活动。为了避免打扰客户,我们允许每个客户指定他们感兴趣的消息。我们还希望将特定的消息发送给特定的客户子集。例如,我们可能只向优先客户宣布特别优惠。当我们需要向多个收件人发送信息时,我们会立即想到发布-订阅通道。然而,发布-订阅通道有一些缺点。首先,它允许任何订阅者在发布者不知情的情况下收听已发布的消息。例如,我们不希望小客户收到针对大批量客户的特别优惠。发布-订阅通道的第二个缺点是它们只能在本地网络上有效地工作。如果我们通过发布-订阅通道跨广域网发送数据我们必须向每个收件人发送单独的消息副本。如果收件人对该消息不感兴趣,我们就会产生不必要的网络流量。

                                                                      In order to im­prove busi­ness, we want to an­nounce spe­cials to our cus­tom­ers every once in a while. To avoid an­noy­ing the cus­tom­ers, we allow each cus­tomer to spe­cify which mes­sages in­terest them. We also want to target spe­cific mes­sages to a spe­cific subset of cus­tom­ers. For ex­ample, we may an­nounce spe­cial deals only to pre­ferred cus­tom­ers. When we need to send in­form­a­tion to mul­tiple re­cip­i­ents, a Pub­lish-Sub­scribe Chan­nel im­me­di­ately comes to mind. How­ever, a Pub­lish-Sub­scribe Chan­nel has some dis­ad­vant­ages. First, it allows any sub­scriber to listen to the pub­lished mes­sages without the pub­lisher's know­ledge. For ex­ample, we would not want smal­ler cus­tom­ers to re­ceive spe­cial offers in­ten­ded for high-volume cus­tom­ers. The second down­side of Pub­lish-Sub­scribe Chan­nels is that they work ef­fi­ciently only on local net­works. If we send data across wide-area net­works via a Pub­lish-Sub­scribe Chan­nel, we have to send a sep­ar­ate copy of the mes­sage to each re­cip­i­ent. If a re­cip­i­ent is not in­ter­ested in the mes­sage, we would have in­curred un­ne­ces­sary net­work traffic.

                                                                      因此,我们应该寻找一种解决方案,允许订阅者发布他们的订阅偏好,然后仅向感兴趣(和授权)的客户发送单独的消息。为了执行此功能,我们使用动态收件人列表。动态收件人列表是两种消息路由模式的组合。收件人列表是一个将单个消息传播到一组收件人的路由器。接收者列表发布-订阅通道之间的主要区别在于,接收者列表专门针对每个收件人,因此可以严格控制谁接收消息。动态路由器是一种其路由算法可以根据控制消息而改变的路由器。这些控制消息可以采用订阅者发出的订阅偏好的形式。动态收件人列表是这两种模式结合的结果

                                                                      There­fore, we should look for a solu­tion that allows sub­scribers to issue their sub­scrip­tion pref­er­ences and then send in­di­vidual mes­sages only to in­ter­ested (and au­thor­ized) cus­tom­ers. To per­form this func­tion, we use a Dy­namic Re­cip­i­ent List. A dy­namic Re­cip­i­ent List is the com­bin­a­tion of two Mes­sage Rout­ing pat­terns. A Re­cip­i­ent List is a router that propag­ates a single mes­sage to a set of re­cip­i­ents. The main dif­fer­ence between the Re­cip­i­ent List and a Pub­lish-Sub­scribe Chan­nel is that the Re­cip­i­ent List ad­dresses each re­cip­i­ent spe­cific­ally and there­fore has tight con­trol over who re­ceives mes­sages. A Dy­namic Router is a router whose rout­ing al­gorithm can change based on con­trol mes­sages. These con­trol mes­sages can take the form of sub­scrip­tion pref­er­ences issued by the sub­scribers. A dy­namic Re­cip­i­ent List is the result of com­bin­ing these two pat­terns

                                                                      发送带有动态收件人列表的公告

                                                                      Send­ing An­nounce­ments with a Dy­namic Re­cip­i­ent List

                                                                      图形/01inf26.gif

                                                                      如果客户通过电子邮件接收公告,则这些模式的实现可以使用通常由电子邮件系统提供的邮件列表功能。然后,每个收件人渠道都通过电子邮件地址进行标识。同样,如果客户更喜欢通过 Web 服务接口接收公告,则每个接收者通道都由 SOAP 请求实现,并且通道地址是 Web 服务的 URI。此示例说明我们用于描述解决方案设计的模式独立于特定的传输技术。

                                                                      If cus­tom­ers re­ceive an­nounce­ments via e-mail, the im­ple­ment­a­tion of these pat­terns can use the mail­ing list's fea­tures typ­ic­ally sup­plied by e-mail sys­tems. Each re­cip­i­ent chan­nel is then iden­ti­fied by an e-mail ad­dress. Like­wise, if cus­tom­ers prefer to re­ceive an­nounce­ments via a Web ser­vices in­ter­face, each re­cip­i­ent chan­nel is im­ple­men­ted by a SOAP re­quest, and the chan­nel ad­dress is the URI of the Web ser­vice. This ex­ample il­lus­trates that the pat­terns we use to de­scribe the solu­tion design are in­de­pend­ent of a spe­cific trans­port tech­no­logy.

                                                                      测试和监控

                                                                      Test­ing and Mon­it­or­ing

                                                                      监视消息的正确执行是一项关键的操作和支持功能。消息存储可以为我们提供一些重要的业务指标,例如履行订单的平均时间。但是,我们可能需要更详细的信息才能成功运行集成解决方案。假设我们增强了解决方案以访问外部信用机构,以更好地评估客户的信用状况。即使我们没有显示未付款项,如果客户的信用评级特别差,我们也可能会拒绝客户的订单。这对于没有我们付款历史的新客户特别有用。由于该服务是由外部提供商提供的,因此我们需要为其使用付费。为了验证提供商的发票,我们希望跟踪我们的实际使用情况并核对两个报告。我们不能简单地看订单数量,因为业务逻辑可能不会要求对长期客户进行外部信用检查。此外,我们可能与外部提供商签订了服务质量 (QoS) 协议。例如,如果响应时间超过指定时间,我们可能无需为请求付费。

                                                                      Mon­it­or­ing the cor­rect ex­e­cu­tion of mes­sages is a crit­ical op­er­a­tions and sup­port func­tion. The Mes­sage Store can provide us with some im­port­ant busi­ness met­rics, such as the av­er­age time to ful­fill an order. How­ever, we may need more de­tailed in­form­a­tion for the suc­cess­ful op­er­a­tion of an in­teg­ra­tion solu­tion. Let's assume we en­hance our solu­tion to access an ex­ternal credit agency to better assess our cus­tomer's credit stand­ing. Even if we show no out­stand­ing pay­ments, we may want to de­cline a cus­tomer's order if the cus­tomer's credit rank­ing is par­tic­u­larly poor. This is es­pe­cially useful for new cus­tom­ers who do not have a pay­ment his­tory with us. Be­cause the ser­vice is provided by an out­side pro­vider, we are charged for its use. To verify the pro­vider's in­voice, we want to track our actual usage and re­con­cile the two re­ports. We cannot simply go by the number of orders, be­cause the busi­ness logic may not re­quest an ex­ternal credit check for long-stand­ing cus­tom­ers. Also, we may have a qual­ity of ser­vice (QoS) agree­ment with the ex­ternal pro­vider. For ex­ample, if the re­sponse time ex­ceeds a spe­cified time, we may not have to pay for the re­quest.

                                                                      为了确保正确计费,我们希望跟踪我们发出的请求数量以及相关响应到达所需的时间。我们必须能够处理两种具体情况。首先,外部服务一次可以处理多个请求,因此我们需要能够匹配请求和回复消息。其次,由于我们将外部服务视为企业内部的共享服务,因此我们希望允许服务使用者指定一个Return Address ,即服务应发送回复消息的通道。不知道回复发送到哪个通道可能会导致匹配请求和回复消息变得困难。

                                                                      To make sure we are being billed cor­rectly, we want to track the number of re­quests we make and the time it takes for the as­so­ci­ated re­sponse to arrive. We have to be able to deal with two spe­cific situ­ations. First, the ex­ternal ser­vice can pro­cess more than one re­quest at a time, so we need to be able to match up re­quest and reply mes­sages. Second, since we treat the ex­ternal ser­vice as a shared ser­vice inside our en­ter­prise, we want to allow the ser­vice con­sumer to spe­cify a Return Ad­dress, the chan­nel where the ser­vice should send the reply mes­sage. Not know­ing to which chan­nel the reply is being sent can make it dif­fi­cult to match re­quest and reply mes­sages.

                                                                      智能代理再次是答案。我们在任何服务使用者和外部服务之间插入智能代理。智能代理拦截对服务的每个请求,并用固定的回复通道替换服务消费者指定的返回地址。这会导致服务将所有回复消息发送到智能代理指定的通道。代理存储原始的返回地址,以便它可以将回复消息转发到消费者最初指定的通道。智能代理还测量来自外部服务的请求和回复消息之间经过的时间。这智能代理将此数据发布到控制总线。控制总线连接到管理控制台,该控制台从许多不同的组件收集指标。

                                                                      Once again, the Smart Proxy is the answer. We insert the Smart Proxy between any ser­vice con­sumer and the ex­ternal ser­vice. The Smart Proxy in­ter­cepts each re­quest to the ser­vice and re­places the Return Ad­dress spe­cified by the ser­vice con­sumer with a fixed reply chan­nel. This causes the ser­vice to send all reply mes­sages to the chan­nel spe­cified by the Smart Proxy. The proxy stores the ori­ginal Return Ad­dress so that it can for­ward the reply mes­sage to the chan­nel ori­gin­ally spe­cified by the con­sumer. The Smart Proxy also meas­ures the time elapsed between re­quest and reply mes­sages from the ex­ternal ser­vice. The Smart Proxy pub­lishes this data to the Con­trol Bus. The Con­trol Bus con­nects to a man­age­ment con­sole that col­lects met­rics from many dif­fer­ent com­pon­ents.

                                                                      插入智能代理来跟踪响应时间

                                                                      In­sert­ing a Smart Proxy to Track Re­sponse Times

                                                                      图形/01inf27.gif

                                                                      除了跟踪我们对外部信用服务的使用情况外,我们还希望确保该服务正常运行。智能代理可以向管理控制台报告在指定超时时间内未收到回复消息的情况。外部服务返回回复消息但消息中的结果不正确的情况更难以检测。例如,如果外部服务出现故障并为每个客户返回零信用评分,我们最终会拒绝每个订单。有两种机制可以帮助我们防止这种情况的发生。首先,我们可以定期将测试消息注入到请求流中。此测试消息请求特定人员的分数以便知道结果。然后,我们可以使用测试数据验证器不仅检查是否收到回复,还检查消息内容的准确性。由于智能代理支持返回地址,测试数据生成器可以指定一个特殊的回复通道来将测试回复与常规回复分开(见图)。

                                                                      Be­sides track­ing our usage of the ex­ternal credit ser­vice, we also want to make sure that the ser­vice is work­ing cor­rectly. The Smart Proxy can report to the man­age­ment con­sole cases where no reply mes­sage is re­ceived within a spe­cified time-out period. Much harder to detect are cases where the ex­ternal ser­vice re­turns a reply mes­sage but the res­ults in the mes­sage are in­cor­rect. For ex­ample, if the ex­ternal ser­vice mal­func­tions and re­turns a credit score of zero for every cus­tomer, we would end up deny­ing every order. There are two mech­an­isms that can help us pro­tect against such a scen­ario. First, we can peri­od­ic­ally inject a Test Mes­sage into the re­quest stream. This Test Mes­sage re­quests the score for a spe­cific person so that the result is known. We can then use a test data veri­fier to check not only that a reply was re­ceived but also the ac­cur­acy of the mes­sage con­tent. Be­cause the Smart Proxy sup­ports Return Ad­dresses, the test data gen­er­ator can spe­cify a spe­cial reply chan­nel to sep­ar­ate test replies from reg­u­lar replies (see figure).

                                                                      插入测试消息以验证结果是否准确

                                                                      In­sert­ing Test Mes­sages to Verify Ac­cur­ate Res­ults

                                                                      图形/01inf28.gif

                                                                      检测故障服务的另一个有效策略是进行统计样本。例如,由于客户信誉不佳,我们预计平均会拒绝不到十分之一的订单。如果我们连续拒绝超过五个订单,这可能表明外部服务或某些业务逻辑出现故障。管理控制台可以通过电子邮件将五个订单发送给管理员,然后管理员可以快速查看数据以验证拒绝是否合理。

                                                                      An­other ef­fect­ive strategy to detect mal­func­tion­ing ser­vices is to take a stat­ist­ical sample. For ex­ample, we may expect to de­cline an av­er­age of less than one in 10 orders due to cus­tom­ers' poor stand­ing. If we de­cline more than five orders in a row, this may be an in­dic­a­tion that an ex­ternal ser­vice or some busi­ness logic is mal­func­tion­ing. The man­age­ment con­sole could e-mail the five orders to an ad­min­is­trator, who can then take a quick look at the data to verify whether the re­jec­tions were jus­ti­fied.

                                                                        概括

                                                                        Summary

                                                                        我们已经使用不同的集成策略(例如文件传输、共享数据库和异步消息传递)完成了相当广泛的集成场景。我们路由、拆分和聚合消息。我们引入了流程管理器以提供更大的灵活性。我们还添加了监控解决方案正确运行的功能。虽然这个示例的要求确实得到了简化,但我们必须考虑的问题和设计权衡却是非常现实的。解决方案图和描述重点介绍了我们如何使用供应商中立和技术中立的语言来描述每个解决方案,这种语言比高级序列图准确得多。

                                                                        We have walked through a fairly ex­tens­ive in­teg­ra­tion scen­ario using dif­fer­ent in­teg­ra­tion strategies such as File Trans­fer, Shared Data­base, and asyn­chron­ous Mes­saging. We routed, split, and ag­greg­ated mes­sages. We in­tro­duced a Pro­cess Man­ager to allow for more flex­ib­il­ity. We also added func­tions to mon­itor the cor­rect op­er­a­tion of the solu­tion. While the re­quire­ments for this ex­ample were ad­mit­tedly sim­pli­fied, the issues and design trade-offs we had to con­sider are very real. The solu­tion dia­grams and de­scrip­tions high­light how we can de­scribe each solu­tion in a vendor-neut­ral and tech­no­logy-neut­ral lan­guage that is much more ac­cur­ate than a high-level se­quence dia­gram.

                                                                        本章中的集成场景主要关注如何连接现有应用程序。有关如何从自定义应用程序内部发布和使用消息的详细说明,请参阅第 6 章“Interlude:简单消息传递”和第 9 章“Interlude:组合消息传递”中的示例。

                                                                        The in­teg­ra­tion scen­ario in this chapter fo­cuses primar­ily on how to con­nect ex­ist­ing ap­plic­a­tions. For a de­tailed de­scrip­tion of how to pub­lish and con­sume mes­sages from inside a custom ap­plic­a­tion, see the ex­amples in Chapter 6, "In­ter­lude: Simple Mes­saging," and Chapter 9, "In­ter­lude: Com­posed Mes­saging."

                                                                        本书的其余部分包含我们在解决方案设计中使用的每种模式的详细描述和代码示例,以及许多相关模式。这些模式按其主要意图分为基本模式、通道模式、消息模式、路由模式、转换模式、端点模式和系统管理模式。这种安排可以轻松地按顺序读取所有模式或查找单个模式作为参考。

                                                                        The re­mainder of the book con­tains de­tailed de­scrip­tions and code ex­amples for each of the pat­terns that we used in our solu­tion design, as well as many re­lated pat­terns. The pat­terns are cat­egor­ized by their primary intent between base pat­terns, chan­nel pat­terns, mes­sage pat­terns, rout­ing pat­terns, trans­form­a­tion pat­terns, en­d­point pat­terns, and system man­age­ment pat­terns. This ar­range­ment makes it easy to read all pat­terns in se­quence or to look up in­di­vidual pat­terns as a ref­er­ence.

                                                                          介绍

                                                                          Introduction

                                                                          企业集成的任务是使不同的应用程序协同工作以产生一组统一的功能。这些应用程序可以在内部定制开发或从第三方供应商处购买。它们可能在多台计算机上运行,​​这些计算机可能代表多个平台,并且可能在地理上分散。某些应用程序可能由业务合作伙伴或客户在企业外部运行。其他应用程序在设计时可能没有考虑到集成,并且很难更改。这些问题和其他类似问题使应用程序集成变得复杂。本章探讨了有助于克服这些挑战的多种集成方法。

                                                                          En­ter­prise in­teg­ra­tion is the task of making dis­par­ate ap­plic­a­tions work to­gether to pro­duce a uni­fied set of func­tion­al­ity. These ap­plic­a­tions can be custom de­ve­loped in house or pur­chased from third-party vendors. They likely run on mul­tiple com­puters, which may rep­res­ent mul­tiple plat­forms, and may be geo­graph­ic­ally dis­persed. Some of the ap­plic­a­tions may be run out­side of the en­ter­prise by busi­ness part­ners or cus­tom­ers. Other ap­plic­a­tions might not have been de­signed with in­teg­ra­tion in mind and are dif­fi­cult to change. These issues and others like them make ap­plic­a­tion in­teg­ra­tion com­plic­ated. This chapter ex­plores mul­tiple in­teg­ra­tion ap­proaches that can help over­come these chal­lenges.

                                                                          应用程序集成标准

                                                                          Ap­plic­a­tion In­teg­ra­tion Cri­teria

                                                                          什么是良好的应用程序集成?如果集成需求始终相同,那么就只有一种集成方式。然而,与任何复杂的技术工作一样,应用程序集成涉及一系列考虑因素和后果,任何集成机会都应考虑到这些因素和后果。

                                                                          What makes good ap­plic­a­tion in­teg­ra­tion? If in­teg­ra­tion needs were always the same, there would be only one in­teg­ra­tion style. Yet, like any com­plex tech­no­lo­gical effort, ap­plic­a­tion in­teg­ra­tion in­volves a range of con­sid­er­a­tions and con­se­quences that should be taken into ac­count for any in­teg­ra­tion op­por­tun­ity.

                                                                          根本标准是是否使用应用程序集成。如果您可以开发一个不需要与任何其他应用程序协作的独立应用程序,则可以完全避免整个集成问题。但实际上,即使是一个简单的企业也有多个应用程序需要协同工作,以便为企业的员工、合作伙伴和客户提供统一的体验。

                                                                          The fun­da­mental cri­terion is whether to use ap­plic­a­tion in­teg­ra­tion at all. If you can de­velop a single, stan­dalone ap­plic­a­tion that doesn't need to col­lab­or­ate with any other ap­plic­a­tions, you can avoid the whole in­teg­ra­tion issue en­tirely. Real­ist­ic­ally, though, even a simple en­ter­prise has mul­tiple ap­plic­a­tions that need to work to­gether to provide a uni­fied ex­per­i­ence for the en­ter­prise's em­ploy­ees, part­ners, and cus­tom­ers.

                                                                          以下是其他一些主要决策标准。

                                                                          The fol­low­ing are some other main de­cision cri­teria.

                                                                          应用程序 耦合应用程序应该最大限度地减少彼此之间的依赖性,以便每个应用程序都可以在不给其他应用程序带来问题的情况下发展。正如第 1 章“使用模式解决集成问题”中所解释的,紧密耦合的应用程序对其他应用程序如何工作做出了许多假设;当应用程序发生变化并打破这些假设时,它们之间的集成就会中断。因此,用于集成应用程序的接口应该足够具体以实现有用的功能,但也应该足够通用以允许实现根据需要进行更改。

                                                                          侵入性 将应用程序 集成,开发人员应努力尽量减少对应用程序的更改以及所需的集成代码量。然而,为了提供良好的集成功能,通常需要进行更改和新代码,并且对应用程序影响最小的方法可能无法提供与企业的最佳集成。

                                                                          技术选择 不同的集成技术需要不同数量的专用软件和硬件。此类工具可能价格昂贵,可能导致供应商锁定,并且会增加开发人员的学习曲线。另一方面,从头开始创建集成解决方案通常会比最初预期付出更多的努力,并且可能意味着重新发明轮子。

                                                                          数据格式 集成应用程序必须就它们交换的数据的格式达成一致。更改现有应用程序以使用统一的数据格式可能很困难或不可能。或者,中间转换器可以统一坚持不同数据格式的应用程序。一个相关的问题是数据格式的演变和可扩展性,格式如何随时间变化以及这种变化将如何影响应用程序。

                                                                          数据时效性 集成应最大限度地缩短一个应用程序决定共享某些数据与其他应用程序拥有该数据之间的时间长度。这可以通过频繁且小块地交换数据来实现。然而,将大量数据分成小块可能会导致效率低下。数据共享的延迟必须考虑到集成设计中。理想情况下,一旦共享数据可供使用,接收器应用程序就应该得到通知。共享时间越长,应用程序不同步的机会就越大,集成也就越复杂。

                                                                          数据或功能 许多 集成解决方案允许应用程序不仅共享数据,还允许共享功能,因为共享功能可以在应用程序之间提供更好的抽象。尽管调用远程应用程序中的功能可能看起来与调用本地功能相同,但其工作方式却截然不同,这对集成的工作效果产生了重大影响。

                                                                          远程通信计算机 处理通常是同步的,即过程在其子过程执行时等待。然而,调用远程子过程比本地子过程慢得多,因此过程可能不想等待子过程完成;相反,它可能希望异步调用子过程,即启动子过程但同时继续其自己的处理。异步性可以提供更高效的解决方案,但这样的解决方案的设计、开发和调试也更加复杂。

                                                                          可靠性 远程 连接不仅速度慢,而且可靠性远低于本地函数调用。当一个过程调用单个应用程序内的子过程时,就假定该子过程可用。远程通信时不一定如此;远程应用程序甚至可能没有运行,或者网络可能暂时不可用。可靠的异步通信使源应用程序能够继续执行其他工作,并确信远程应用程序稍后会采取行动。

                                                                          Ap­plic­a­tion coup­ling In­teg­rated ap­plic­a­tions should min­im­ize their de­pend­en­cies on each other so that each can evolve without caus­ing prob­lems to the others. As ex­plained in Chapter 1, "Solv­ing In­teg­ra­tion Prob­lems Using Pat­terns," tightly coupled ap­plic­a­tions make nu­mer­ous as­sump­tions about how the other ap­plic­a­tions work; when the ap­plic­a­tions change and break those as­sump­tions, the in­teg­ra­tion between them breaks. There­fore, the in­ter­faces for in­teg­rat­ing ap­plic­a­tions should be spe­cific enough to im­ple­ment useful func­tion­al­ity but gen­eral enough to allow the im­ple­ment­a­tion to change as needed.

                                                                          In­trus­ive­ness When in­teg­rat­ing an ap­plic­a­tion into an en­ter­prise, de­ve­lopers should strive to min­im­ize both changes to the ap­plic­a­tion and the amount of in­teg­ra­tion code needed. Yet, changes and new code are often ne­ces­sary to provide good in­teg­ra­tion func­tion­al­ity, and the ap­proaches with the least impact on the ap­plic­a­tion may not provide the best in­teg­ra­tion into the en­ter­prise.

                                                                          Tech­no­logy se­lec­tion Dif­fer­ent in­teg­ra­tion tech­niques re­quire vary­ing amounts of spe­cial­ized soft­ware and hard­ware. Such tools can be ex­pens­ive, can lead to vendor lock-in, and can in­crease the learn­ing curve for de­ve­lopers. On the other hand, cre­at­ing an in­teg­ra­tion solu­tion from scratch usu­ally res­ults in more effort than ori­gin­ally in­ten­ded and can mean re­in­vent­ing the wheel.

                                                                          Data format In­teg­rated ap­plic­a­tions must agree on the format of the data they ex­change. Chan­ging ex­ist­ing ap­plic­a­tions to use a uni­fied data format may be dif­fi­cult or im­pos­sible. Al­tern­at­ively, an in­ter­me­di­ate trans­lator can unify ap­plic­a­tions that insist on dif­fer­ent data formats. A re­lated issue is data format evol­u­tion and ex­tens­ib­il­ityhow the format can change over time and how that change will affect the ap­plic­a­tions.

                                                                          Data timeli­ness In­teg­ra­tion should min­im­ize the length of time between when one ap­plic­a­tion de­cides to share some data and other ap­plic­a­tions have that data. This can be ac­com­plished by ex­chan­ging data fre­quently and in small chunks. How­ever, chunking a large set of data into small pieces may in­tro­duce in­ef­fi­cien­cies. Latency in data shar­ing must be factored into the in­teg­ra­tion design. Ideally, re­ceiver ap­plic­a­tions should be in­formed as soon as shared data is ready for con­sump­tion. The longer shar­ing takes, the greater the op­por­tun­ity for ap­plic­a­tions to get out of sync and the more com­plex in­teg­ra­tion can become.

                                                                          Data or func­tion­al­ity Many in­teg­ra­tion solu­tions allow ap­plic­a­tions to share not only data but func­tion­al­ity as well, be­cause shar­ing of func­tion­al­ity can pro­vider better ab­strac­tion between the ap­plic­a­tions. Even though in­vok­ing func­tion­al­ity in a remote ap­plic­a­tion may seem the same as in­vok­ing local func­tion­al­ity, it works quite dif­fer­ently, with sig­ni­fic­ant con­se­quences for how well the in­teg­ra­tion works.

                                                                          Remote Com­mu­nic­a­tion Com­puter pro­cess­ing is typ­ic­ally syn­chron­ousthat is, a pro­ced­ure waits while its sub­pro­ced­ure ex­ecutes. How­ever, call­ing a remote sub­pro­ced­ure is much slower than a local one so that a pro­ced­ure may not want to wait for the sub­pro­ced­ure to com­plete; in­stead, it may want to invoke the sub­pro­ced­ure asyn­chron­ously, that is, start­ing the sub­pro­ced­ure but con­tinu­ing with its own pro­cess­ing sim­ul­tan­eously. Asyn­chron­icity can make for a much more ef­fi­cient solu­tion, but such a solu­tion is also more com­plex to design, de­velop, and debug.

                                                                          Re­li­ab­il­ity Remote con­nec­tions are not only slow, but they are much less re­li­able than a local func­tion call. When a pro­ced­ure calls a sub­pro­ced­ure inside a single ap­plic­a­tion, it's a given that the sub­pro­ced­ure is avail­able. This is not ne­ces­sar­ily true when com­mu­nic­at­ing re­motely; the remote ap­plic­a­tion may not even be run­ning or the net­work may be tem­por­ar­ily un­avail­able. Re­li­able, asyn­chron­ous com­mu­nic­a­tion en­ables the source ap­plic­a­tion to go on to other work, con­fid­ent that the remote ap­plic­a­tion will act some­time later.

                                                                          因此,正如您所看到的,在选择和设计集成方法时必须考虑几个不同的标准。那么问题就变成了,哪种集成方法最能满足这些标准中的哪一个?

                                                                          So, as you can see, there are sev­eral dif­fer­ent cri­teria that must be con­sidered when choos­ing and design­ing an in­teg­ra­tion ap­proach. The ques­tion then be­comes, Which in­teg­ra­tion ap­proach best ad­dresses which of these cri­teria?

                                                                          应用程序集成选项

                                                                          Ap­plic­a­tion In­teg­ra­tion Op­tions

                                                                          没有一种集成方法能够同等地满足所有标准。因此,随着时间的推移,集成应用程序的多种方法不断发展。各种方法可以概括为四种主要集成风格。

                                                                          There is no one in­teg­ra­tion ap­proach that ad­dresses all cri­teria equally well. There­fore, mul­tiple ap­proaches for in­teg­rat­ing ap­plic­a­tions have evolved over time. The vari­ous ap­proaches can be summed up in four main in­teg­ra­tion styles.

                                                                          文件传输

                                                                          共享数据库

                                                                          远程过程调用 让 每个

                                                                          消息传递

                                                                          File Trans­fer Have each ap­plic­a­tion pro­duce files of shared data for others to con­sume and con­sume files that others have pro­duced.

                                                                          Shared Data­base Have the ap­plic­a­tions store the data they wish to share in a common data­base.

                                                                          Remote Pro­ced­ure In­voc­a­tion Have each ap­plic­a­tion expose some of its pro­ced­ures so that they can be in­voked re­motely, and have ap­plic­a­tions invoke those to ini­ti­ate be­ha­vior and ex­change data.

                                                                          Mes­saging Have each ap­plic­a­tion con­nect to a common mes­saging system, and ex­change data and invoke be­ha­vior using mes­sages.

                                                                          本章将每种样式作为模式呈现。这四种模式共享相同的问题陈述——集成应用程序的需要和非常相似的上下文。他们的与众不同之处在于寻求更优雅的解决方案的力量。每一种模式都建立在上一种模式的基础上,寻找一种更复杂的方法来解决其前身的缺点。因此,模式顺序反映了复杂性的增加,但也反映了复杂性的增加。

                                                                          This chapter presents each style as a pat­tern. The four pat­terns share the same prob­lem state­mentthe need to in­teg­rate ap­plic­a­tion­sand very sim­ilar con­texts. What dif­fer­en­ti­ates them are the forces search­ing for a more el­eg­ant solu­tion. Each pat­tern builds on the last, look­ing for a more soph­ist­ic­ated ap­proach to ad­dress the short­com­ings of its pre­de­cessors. Thus, the pat­tern order re­flects an in­creas­ing order of soph­ist­ic­a­tion, but also in­creas­ing com­plex­ity.

                                                                          诀窍不是每次都选择一种样式,而是针对特定的集成机会选择最佳样式。每种风格都有其优点和缺点。应用程序可以使用多种样式进行集成,以便每个集成点都能利用最适合它的样式。同样,应用程序可以使用不同的样式与不同的应用程序集成,选择最适合其他应用程序的样式。因此,许多集成方法最好被视为多种集成风格的混合体。为了支持这种类型的集成,许多集成和 EAI 中间件产品采用了多种样式的组合,所有这些样式都有效地隐藏在产品的实现中。

                                                                          The trick is not to choose one style to use every time but to choose the best style for a par­tic­u­lar in­teg­ra­tion op­por­tun­ity. Each style has its ad­vant­ages and dis­ad­vant­ages. Ap­plic­a­tions may in­teg­rate using mul­tiple styles so that each point of in­teg­ra­tion takes ad­vant­age of the style that suits it best. Like­wise, an ap­plic­a­tion may use dif­fer­ent styles to in­teg­rate with dif­fer­ent ap­plic­a­tions, choos­ing the style that works best for the other ap­plic­a­tion. As a result, many in­teg­ra­tion ap­proaches can best be viewed as a hybrid of mul­tiple in­teg­ra­tion styles. To sup­port this type of in­teg­ra­tion, many in­teg­ra­tion and EAI mid­dle­ware products employ a com­bin­a­tion of styles, all of which are ef­fect­ively hidden in the product's im­ple­ment­a­tion.

                                                                          本书其余部分的模式扩展了消息传递集成风格。我们专注于消息传递,因为我们相信它在集成标准之间提供了良好的平衡,但也是最难使用的风格。因此,消息传递仍然是集成风格中最难理解的技术,也是一种成熟的技术,其模式可以快速解释如何最好地使用它。最后,消息传递是许多商业 EAI 产品的基础,因此解释如何很好地使用消息传递对于教会您如何使用这些产品也大有帮助。本节的重点是强调与应用程序集成相关的问题以及消息传递如何融入其中。

                                                                          The pat­terns in the re­mainder of this book expand on the Mes­saging in­teg­ra­tion style. We focus on mes­saging be­cause we be­lieve that it provides a good bal­ance between the in­teg­ra­tion cri­teria but is also the most dif­fi­cult style to work with. As a result, mes­saging is still the least well un­der­stood of the in­teg­ra­tion styles and a tech­no­logy ripe with pat­terns that quickly ex­plain how to use it best. Fi­nally, mes­saging is the basis for many com­mer­cial EAI products, so ex­plain­ing how to use mes­saging well also goes a long way in teach­ing you how to use those products. The focus of this sec­tion is to high­light the issues in­volved with ap­plic­a­tion in­teg­ra­tion and how mes­saging fits into the mix.

                                                                            文件传输

                                                                            File Transfer

                                                                            图形/filetransfer_icon.gif

                                                                            马丁·福勒

                                                                            by Martin Fowler

                                                                            一个企业有多个独立构建的应用程序,这些应用程序使用不同的语言和平台。

                                                                            An en­ter­prise has mul­tiple ap­plic­a­tions that are being built in­de­pend­ently, with dif­fer­ent lan­guages and plat­forms.

                                                                            如何集成多个应用程序以便它们协同工作并交换信息?

                                                                            How can I in­teg­rate mul­tiple ap­plic­a­tions so that they work to­gether and can ex­change in­form­a­tion?



                                                                            在理想的世界中,您可能会想象一个组织通过一个单一的、有凝聚力的软件进行操作,该软件从一开始就被设计为以统一和连贯的方式工作。当然,即使是最小的操作也不会这样工作。多个软件处理企业的不同方面。这是由于多种原因造成的。

                                                                            In an ideal world, you might ima­gine an or­gan­iz­a­tion op­er­at­ing from a single, co­hes­ive piece of soft­ware, de­signed from the be­gin­ning to work in a uni­fied and co­her­ent way. Of course, even the smal­lest op­er­a­tions don't work like that. Mul­tiple pieces of soft­ware handle dif­fer­ent as­pects of the en­ter­prise. This is due to a host of reas­ons.

                                                                            • 人们购买由外部组织开发的软件包。

                                                                            • People buy pack­ages that are de­ve­loped by out­side or­gan­iz­a­tions.

                                                                            • 不同的系统在不同的时间构建,导致不同的技术选择。

                                                                            • Dif­fer­ent sys­tems are built at dif­fer­ent times, lead­ing to dif­fer­ent tech­no­logy choices.

                                                                            • 不同的系统是由不同的人构建的,他们的经验和偏好导致他们采用不同的方法来构建应用程序。

                                                                            • Dif­fer­ent sys­tems are built by dif­fer­ent people whose ex­per­i­ence and pref­er­ences lead them to dif­fer­ent ap­proaches to build­ing ap­plic­a­tions.

                                                                            • 推出应用程序并交付价值比确保解决集成问题更重要,尤其是当集成不会为正在开发的应用程序增加任何价值时。

                                                                            • Get­ting an ap­plic­a­tion out and de­liv­er­ing value is more im­port­ant than en­sur­ing that in­teg­ra­tion is ad­dressed, es­pe­cially when that in­teg­ra­tion doesn't add any value to the ap­plic­a­tion under de­vel­op­ment.

                                                                            因此,任何组织都必须担心非常不同的应用程序之间共享信息。它们可以基于不同的平台用不同的语言编写,并且对业务运作方式有不同的假设。

                                                                            As a result, any or­gan­iz­a­tion has to worry about shar­ing in­form­a­tion between very di­ver­gent ap­plic­a­tions. These can be writ­ten in dif­fer­ent lan­guages, based on dif­fer­ent plat­forms, and have dif­fer­ent as­sump­tions about how the busi­ness op­er­ates.

                                                                            将此类应用程序连接在一起需要彻底了解如何在业务和技术层面上将应用程序连接在一起。如果您最大限度地减少需要了解每个应用程序如何工作的信息,这会容易得多。

                                                                            Tying to­gether such ap­plic­a­tions re­quires a thor­ough un­der­stand­ing of how to link to­gether ap­plic­a­tions on both the busi­ness and tech­nical levels. This is a lot easier if you min­im­ize what you need to know about how each ap­plic­a­tion works.

                                                                            我们需要的是一种通用的数据传输机制,可以被多种语言和平台使用,但对每种语言和平台来说都感觉很自然。它应该需要最少量的专用硬件和软件,并利用企业现有的资源。

                                                                            What is needed is a common data trans­fer mech­an­ism that can be used by a vari­ety of lan­guages and plat­forms but that feels nat­ural to each. It should re­quire a min­imal amount of spe­cial­ized hard­ware and soft­ware, making use of what the en­ter­prise already has avail­able.

                                                                            文件是一种通用存储机制,内置于任何企业操作系统中,并且可通过任何企业语言使用。最简单的方法是以某种方式使用文件集成应用程序。

                                                                            Files are a uni­ver­sal stor­age mech­an­ism, built into any en­ter­prise op­er­at­ing system and avail­able from any en­ter­prise lan­guage. The simplest ap­proach would be to some­how in­teg­rate the ap­plic­a­tions using files.

                                                                            让每个应用程序生成包含其他应用程序必须使用的信息的文件。集成商负责将文件转换为不同的格式。根据业务性质定期制作文件。

                                                                            Have each ap­plic­a­tion pro­duce files that con­tain the in­form­a­tion the other ap­plic­a­tions must con­sume. In­teg­rat­ors take the re­spons­ib­il­ity of trans­form­ing files into dif­fer­ent formats. Pro­duce the files at reg­u­lar in­ter­vals ac­cord­ing to the nature of the busi­ness.

                                                                            图形/02inf01.gif



                                                                            文件的一个重要决定是使用什么格式。一个应用程序的输出很少与另一个应用程序所需的完全一致,因此您必须在此过程中对文件进行大量处理。这意味着不仅所有使用文件的应用程序都必须读取该文件,而且您还必须能够对其使用处理工具。因此,标准文件格式随着时间的推移而不断发展。大型机系统通常使用基于 COBOL 文件系统格式的数据源。UNIX 系统使用基于文本的文件。目前的方法是使用XML。围绕这些格式建立了一个由读者、作家和转换工具组成的行业。

                                                                            An im­port­ant de­cision with files is what format to use. Very rarely will the output of one ap­plic­a­tion be ex­actly what's needed for an­other, so you'll have to do a fair bit of pro­cess­ing of files along the way. This means not only that all the ap­plic­a­tions that use a file have to read it, but that you also have to be able to use pro­cess­ing tools on it. As a result, stand­ard file formats have grown up over time. Main­frame sys­tems com­monly use data feeds based on the file system formats of COBOL. UNIX sys­tems use text-based files. The cur­rent method is to use XML. An in­dustry of read­ers, writers, and trans­form­a­tion tools has built up around each of these formats.

                                                                            文件的另一个问题是何时生成和使用它们。由于生成和处理文件需要一定的工作量,因此您通常不想太频繁地使用它们。通常,您有一些定期的业务周期来推动决策:每晚、每周、每季度等等。应用程序会习惯新文件何时可用并及时处理它。

                                                                            An­other issue with files is when to pro­duce them and con­sume them. Since there's a cer­tain amount of effort re­quired to pro­duce and pro­cess a file, you usu­ally don't want to work with them too fre­quently. Typ­ic­ally, you have some reg­u­lar busi­ness cycle that drives the de­cision: nightly, weekly, quarterly, and so on. Ap­plic­a­tions get used to when a new file is avail­able and pro­cesses it at its time.

                                                                            文件的巨大优点是集成商不需要了解应用程序的内部结构。应用团队本身通常会提供该文件。文件的内容和格式是与集成商协商的,尽管如果使用包,选择通常是有限的。然后,集成商处理其他应用程序所需的转换,或者将其留给使用应用程序来决定如何操作和读取文件。因此,不同的应用程序可以很好地相互解耦。每个应用程序都可以自由地进行内部更改,而不会影响其他应用程序,只要它们仍然以相同的格式在文件中生成相同的数据。

                                                                            The great ad­vant­age of files is that in­teg­rat­ors need no know­ledge of the in­tern­als of an ap­plic­a­tion. The ap­plic­a­tion team itself usu­ally provides the file. The file's con­tents and format are ne­go­ti­ated with in­teg­rat­ors, al­though if a pack­age is used, the choices are often lim­ited. The in­teg­rat­ors then deal with the trans­form­a­tions re­quired for other ap­plic­a­tions, or they leave it up to the con­sum­ing ap­plic­a­tions to decide how they want to ma­nip­u­late and read the file. As a result, the dif­fer­ent ap­plic­a­tions are quite nicely de­coupled from each other. Each ap­plic­a­tion can make in­ternal changes freely without af­fect­ing other ap­plic­a­tions, provid­ing they still pro­duce the same data in the files in the same format. The files ef­fect­ively become the public in­ter­face of each ap­plic­a­tion.

                                                                            文件传输的一部分简单的是不需要额外的工具或集成包,但这也意味着开发人员必须自己做很多工作。应用程序必须就文件命名约定及其出现的目录达成一致。文件的编写者必须实施一种策略来保持文件名的唯一性。应用程序必须就哪个文件将删除旧文件达成一致,并且负责该责任的应用程序必须知道文件何时过时且不再需要。应用程序需要实现一种锁定机制或遵循计时约定,以确保一个应用程序在另一个应用程序仍在写入文件时不会尝试读取该文件。如果所有应用程序都无法访问同一磁盘,

                                                                            Part of what makes File Trans­fer simple is that no extra tools or in­teg­ra­tion pack­ages are needed, but that also means that de­ve­lopers have to do a lot of the work them­selves. The ap­plic­a­tions must agree on file-naming con­ven­tions and the dir­ect­or­ies in which they appear. The writer of a file must im­ple­ment a strategy to keep the file names unique. The ap­plic­a­tions must agree on which one will delete old files, and the ap­plic­a­tion with that re­spons­ib­il­ity will have to know when a file is old and no longer needed. The ap­plic­a­tions will need to im­ple­ment a lock­ing mech­an­ism or follow a timing con­ven­tion to ensure that one ap­plic­a­tion is not trying to read the file while an­other is still writ­ing it. If all of the ap­plic­a­tions do not have access to the same disk, then some ap­plic­a­tion must take re­spons­ib­il­ity for trans­fer­ring the file from one disk to an­other.

                                                                            文件传输最明显的问题之一是更新往往很少发生,因此系统可能会不同步。客户管理系统可以处理地址变更并每晚生成提取文件,但计费系统可能会在同一天将账单发送到旧地址。有时缺乏同步并不是什么大问题。人们常常预计,即使使用计算机,获取信息也会有一定的滞后。有时,使用陈旧信息的结果是一场灾难。在决定何时生成文件时,必须考虑消费者的新鲜度需求。

                                                                            One of the most ob­vi­ous issues with File Trans­fer is that up­dates tend to occur in­fre­quently, and as a result sys­tems can get out of syn­chron­iz­a­tion. A cus­tomer man­age­ment system can pro­cess a change of ad­dress and pro­duce an ex­tract file each night, but the billing system may send the bill to an old ad­dress on the same day. Some­times lack of syn­chron­iz­a­tion isn't a big deal. People often expect a cer­tain lag in get­ting in­form­a­tion around, even with com­puters. At other times the result of using stale in­form­a­tion is a dis­aster. When de­cid­ing on when to pro­duce files, you have to take the fresh­ness needs of con­sumers into ac­count.

                                                                            事实上,陈旧的最大问题往往在于软件开发人员本身,他们经常必须处理不太正确的数据。这可能会导致难以解决的不一致。如果一位客户在同一天使用两个不同的系统更改了他的地址,但其中一个系统出错并获取了错误的街道名称,那么您将拥有该客户的两个不同地址。您需要某种方法来找出解决此问题的方法。文件传输之间的时间间隔越长,出现此问题的可能性就越大,也就越令人痛苦。

                                                                            In fact, the biggest prob­lem with stale­ness is often on the soft­ware de­vel­op­ment staff them­selves, who fre­quently must deal with data that isn't quite right. This can lead to in­con­sist­en­cies that are dif­fi­cult to re­solve. If a cus­tomer changes his ad­dress on the same day with two dif­fer­ent sys­tems, but one of them makes an error and gets the wrong street name, you'll have two dif­fer­ent ad­dresses for a cus­tomer. You'll need some way to figure out how to re­solve this. The longer the period between file trans­fers, the more likely and more pain­ful this prob­lem can become.

                                                                            当然,您没有理由不能更频繁地生成文件。事实上,您可以将消息传递视为文件传输,您可以在应用程序中的每次更改时生成一个文件。接下来的问题是管理生成的所有文件,确保它们全部被读取并且不会丢失。这超出了基于文件系统的方法的能力范围,特别是因为处理文件会产生昂贵的资源成本,如果您想快速生成大量文件,这可能会令人望而却步。因此,一旦您获得非常细粒度的文件,就更容易将它们视为消息传递

                                                                            Of course, there's no reason that you can't pro­duce files more fre­quently. Indeed, you can think of Mes­saging as File Trans­fer where you pro­duce a file with every change in an ap­plic­a­tion. The prob­lem then is man­aging all the files that get pro­duced, en­sur­ing that they are all read and that none get lost. This goes beyond what file sys­tem­based ap­proaches can do, par­tic­u­larly since there are ex­pens­ive re­source costs as­so­ci­ated with pro­cess­ing a file, which can get pro­hib­it­ive if you want to pro­duce lots of files quickly. As a result, once you get to very fine-grained files, it's easier to think of them as Mes­saging.

                                                                            为了更快地提供数据并强制执行一组商定的数据格式,请使用共享数据库。要集成应用程序的功能而不是数据,请使用远程过程调用。要频繁交换少量数据(可能用于调用远程功能),请使用消息传递

                                                                            To make data avail­able more quickly and en­force an agreed-upon set of data formats, use a Shared Data­base. To in­teg­rate ap­plic­a­tions' func­tion­al­ity rather than their data, use Remote Pro­ced­ure In­voc­a­tion. To enable fre­quent ex­changes of small amounts of data, per­haps used to invoke remote func­tion­al­ity, use Mes­saging.

                                                                              共享数据库

                                                                              Shared Database

                                                                              图形/shareddatabase_icon.gif

                                                                              马丁·福勒

                                                                              by Martin Fowler

                                                                              一个企业有多个独立构建的应用程序,这些应用程序使用不同的语言和平台。企业需要快速、一致地共享信息。

                                                                              An en­ter­prise has mul­tiple ap­plic­a­tions that are being built in­de­pend­ently, with dif­fer­ent lan­guages and plat­forms. The en­ter­prise needs in­form­a­tion to be shared rap­idly and con­sist­ently.

                                                                              如何集成多个应用程序以便它们协同工作并交换信息?

                                                                              How can I in­teg­rate mul­tiple ap­plic­a­tions so that they work to­gether and can ex­change in­form­a­tion?



                                                                              文件传输使如果更改不能快速通过一系列应用程序,您可能会因数据过时而犯错误。对于现代企业来说,每个人都必须拥有最新的数据。这不仅减少了错误,还增加了人们对数据本身的信任。

                                                                              File Trans­fer en­ables ap­plic­a­tions to share data, but it can lack timeli­nessyet timeli­ness of in­teg­ra­tion is often crit­ical. If changes do not quickly work their way through a family of ap­plic­a­tions, you are likely to make mis­takes due to the stale­ness of the data. For modern busi­nesses, it is im­per­at­ive that every­one have the latest data. This not only re­duces errors, but also in­creases people's trust in the data itself.

                                                                              快速更新还可以更好地处理不一致的情况。同步越频繁,出现不一致的可能性就越小,处理它们的工作量就越少。但无论变化多么快,仍然会出现问题。如果一个地址快速连续更新且不一致,您如何确定哪个是真实地址?您可以获取每条数据并说一个应用程序是该数据的主源,但是您必须记住哪个应用程序是哪个数据的主源。

                                                                              Rapid up­dates also allow in­con­sist­en­cies to be handled better. The more fre­quently you syn­chron­ize, the less likely you are to get in­con­sist­en­cies and the less effort they are to deal with. But how­ever rapid the changes, there are still going to be prob­lems. If an ad­dress is up­dated in­con­sist­ently in rapid suc­ces­sion, how do you decide which one is the true ad­dress? You could take each piece of data and say that one ap­plic­a­tion is the master source for that data, but then you'd have to re­mem­ber which ap­plic­a­tion is the master for which data.

                                                                              文件传输也集成中的许多问题都源于查看数据的方式不兼容。通常,这些代表了可能产生巨大影响的微妙业务问题。地质数据库可以将油井定义为可能产油也可能不产油的单个钻孔。生产数据库可以定义由单件设备覆盖的多个孔。这些语义不一致的情况比不一致的数据格式更难处理。(为了对这些问题进行更深入的讨论,确实值得阅读《数据与现实》[肯特].) 需要的是一个所有应用程序共享的中央一致数据存储,以便每个应用程序在需要时都可以访问任何共享数据。

                                                                              File Trans­fer also may not en­force data format suf­fi­ciently. Many of the prob­lems in in­teg­ra­tion come from in­com­pat­ible ways of look­ing at the data. Often these rep­res­ent subtle busi­ness issues that can have a huge effect. A geo­lo­gical data­base may define an oil well as a single drilled hole that may or may not pro­duce oil. A pro­duc­tion data­base may define a well as mul­tiple holes covered by a single piece of equip­ment. These cases of se­mantic dis­son­ance are much harder to deal with than in­con­sist­ent data formats. (For a much deeper dis­cus­sion of these issues, it's really worth read­ing Data and Real­ity [Kent].) What is needed is a cent­ral, agreed-upon data­store that all of the ap­plic­a­tions share so each has access to any of the shared data whenever it needs it.

                                                                              通过将应用程序的数据存储在单个共享数据库中来集成应用程序,并定义数据库的架构来处理不同应用程序的所有需求。

                                                                              In­teg­rate ap­plic­a­tions by having them store their data in a single Shared Data­base, and define the schema of the data­base to handle all the needs of the dif­fer­ent ap­plic­a­tions.

                                                                              图形/02inf02.gif



                                                                              如果一系列集成应用程序都依赖于同一个数据库,那么您可以非常确定它们始终保持一致。如果您确实从不同来源同时更新了单个数据,那么您拥有的事务管理系统可以尽可能优雅地处理该数据。由于更新之间的时间间隔很短,因此更容易发现和修复任何错误。

                                                                              If a family of in­teg­rated ap­plic­a­tions all rely on the same data­base, then you can be pretty sure that they are always con­sist­ent all of the time. If you do get sim­ul­tan­eous up­dates to a single piece of data from dif­fer­ent sources, then you have trans­ac­tion man­age­ment sys­tems that handle that about as grace­fully as it ever can be man­aged. Since the time between up­dates is so small, any errors are much easier to find and fix.

                                                                              基于 SQL 的关系数据库的广泛使用使共享数据库变得更加容易。几乎所有应用程序开发平台都可以使用 SQL,通常需要使用相当复杂的工具。因此您不必担心多种文件格式。由于任何应用程序几乎都必须使用 SQL,因此这避免了添加另一种让每个人都需要掌握的技术。

                                                                              Shared Data­base is made much easier by the wide­spread use of SQL-based re­la­tional data­bases. Pretty much all ap­plic­a­tion de­vel­op­ment plat­forms can work with SQL, often with quite soph­ist­ic­ated tools. So you don't have to worry about mul­tiple file formats. Since any ap­plic­a­tion pretty much has to use SQL anyway, this avoids adding yet an­other tech­no­logy for every­one to master.

                                                                              由于每个应用程序都使用相同的数据库,因此这就消除了语义不一致的问题。不要让这些问题恶化,直到难以通过转换来解决,而是被迫在软件上线并收集大量不兼容数据之前面对它们并处理它们。

                                                                              Since every ap­plic­a­tion is using the same data­base, this forces out prob­lems in se­mantic dis­son­ance. Rather than leav­ing these prob­lems to fester until they are dif­fi­cult to solve with trans­forms, you are forced to con­front them and deal with them before the soft­ware goes live and you col­lect large amounts of in­com­pat­ible data.

                                                                              共享数据库的最大困难之一为共享数据库提供合适的设计。提出一个可以满足多个应用程序需求的统一模式是一项非常困难的工作,通常会导致应用程序程序员难以使用该模式。如果设计统一模式的技术困难还不够,那么还存在严重的政治困难。如果关键应用程序可能会因使用统一模式而遭受延迟,那么通常会面临不可抗拒的分离压力。部门之间的人为冲突往往会加剧这个问题。

                                                                              One of the biggest dif­fi­culties with Shared Data­base is coming up with a suit­able design for the shared data­base. Coming up with a uni­fied schema that can meet the needs of mul­tiple ap­plic­a­tions is a very dif­fi­cult ex­er­cise, often res­ult­ing in a schema that ap­plic­a­tion pro­gram­mers find dif­fi­cult to work with. And if the tech­nical dif­fi­culties of design­ing a uni­fied schema aren't enough, there are also severe polit­ical dif­fi­culties. If a crit­ical ap­plic­a­tion is likely to suffer delays in order to work with a uni­fied schema, then often there is ir­res­ist­ible pres­sure to sep­ar­ate. Human con­flicts between de­part­ments often ex­acer­bate this prob­lem.

                                                                              对共享数据库的另一个更严格的限制外部包。大多数打包应用程序无法使用除其自身架构之外的架构。即使有一定的适应空间,也可能比集成商希望的要有限得多。更糟糕的是,软件供应商通常保留在每个新版本的软件中更改架构的权利。

                                                                              An­other, harder limit to Shared Data­base is ex­ternal pack­ages. Most pack­aged ap­plic­a­tions won't work with a schema other than their own. Even if there is some room for ad­apt­a­tion, it's likely to be much more lim­ited than in­teg­rat­ors would like. Adding to the prob­lem, soft­ware vendors usu­ally re­serve the right to change the schema with every new re­lease of the soft­ware.

                                                                              这个问题还延伸到开发后的整合。即使您可以组织所有应用程序,如果发生公司合并,您仍然会遇到集成问题。

                                                                              This prob­lem also ex­tends to in­teg­ra­tion after de­vel­op­ment. Even if you can or­gan­ize all your ap­plic­a­tions, you still have an in­teg­ra­tion prob­lem should a merger of com­pan­ies occur.

                                                                              使用共享数据库的多个应用程序频繁读取和修改相同的数据可能会将数据库变成性能瓶颈,并且可能会导致死锁,因为每个应用程序都将其他应用程序锁定在数据之外。当应用程序分布在多个位置时,通过广域网访问单个共享数据库通常速度太慢而不实用。分布式数据库也允许每个应用程序通过本地网络连接访问数据库,但混淆了数据应该存储在哪台计算机上的问题。具有锁定冲突的分布式数据库很容易成为性能噩梦。

                                                                              Mul­tiple ap­plic­a­tions using a Shared Data­base to fre­quently read and modify the same data can turn the data­base into a per­form­ance bot­tle­neck and can cause dead­locks as each ap­plic­a­tion locks others out of the data. When ap­plic­a­tions are dis­trib­uted across mul­tiple loc­a­tions, ac­cess­ing a single, shared data­base across a wide-area net­work is typ­ic­ally too slow to be prac­tical. Dis­trib­ut­ing the data­base as well allows each ap­plic­a­tion to access the data­base via a local net­work con­nec­tion, but con­fuses the issue of which com­puter the data should be stored on. A dis­trib­uted data­base with lock­ing con­flicts can easily become a per­form­ance night­mare.

                                                                              要集成应用程序的功能而不是数据,请使用远程过程调用。要使用每种数据类型的格式而不是一种通用模式来频繁交换少量数据,请使用Messaging

                                                                              To in­teg­rate ap­plic­a­tions' func­tion­al­ity rather than their data, use Remote Pro­ced­ure In­voc­a­tion. To enable fre­quent ex­changes of small amounts of data using a format per data­type rather than one uni­ver­sal schema, use Mes­saging.

                                                                                远程过程调用

                                                                                Remote Procedure Invocation

                                                                                图形/remoteprocedure_icon.gif

                                                                                马丁·福勒

                                                                                by Martin Fowler

                                                                                一个企业有多个独立构建的应用程序,这些应用程序使用不同的语言和平台。企业需要以响应方式共享数据和流程。

                                                                                An en­ter­prise has mul­tiple ap­plic­a­tions that are being built in­de­pend­ently, with dif­fer­ent lan­guages and plat­forms. The en­ter­prise needs to share data and pro­cesses in a re­spons­ive way.

                                                                                如何集成多个应用程序以便它们协同工作并交换信息?

                                                                                How can I in­teg­rate mul­tiple ap­plic­a­tions so that they work to­gether and can ex­change in­form­a­tion?



                                                                                文件传输共享数据库使数据更改通常需要跨不同应用程序采取操作。例如,更改地址可能是简单的数据更改,也可能触发注册和法律程序,以考虑不同法律管辖区的不同规则。让一个应用程序直接在其他应用程序中调用此类进程将需要应用程序对其他应用程序的内部了解太多。

                                                                                File Trans­fer and Shared Data­base enable ap­plic­a­tions to share their data, which is an im­port­ant part of ap­plic­a­tion in­teg­ra­tion, but just shar­ing data is often not enough. Changes in data often re­quire ac­tions to be taken across dif­fer­ent ap­plic­a­tions. For ex­ample, chan­ging an ad­dress may be a simple change in data, or it may trig­ger re­gis­tra­tion and legal pro­cesses to take into ac­count dif­fer­ent rules in dif­fer­ent legal jur­is­dic­tions. Having one ap­plic­a­tion invoke such pro­cesses dir­ectly in others would re­quire ap­plic­a­tions to know far too much about the in­tern­als of other ap­plic­a­tions.

                                                                                这个问题反映了应用程序设计中的一个经典困境。应用程序设计中最强大的结构化机制之一是封装,其中模块通过函数调用接口隐藏其数据。通过这种方式,他们可以拦截数据的变化,以在数据变化时执行他们需要执行的各种操作。共享数据库提供了一个大型的、未封装的数据结构,这使得做到这一点变得更加困难。文件传输允许应用程序在处理文件时对更改做出反应,但该过程会被延迟。

                                                                                This prob­lem mir­rors a clas­sic di­lemma in ap­plic­a­tion design. One of the most power­ful struc­tur­ing mech­an­isms in ap­plic­a­tion design is en­cap­su­la­tion, where mod­ules hide their data through a func­tion call in­ter­face. In this way, they can in­ter­cept changes in data to carry out the vari­ous ac­tions they need to per­form when the data is changed. Shared Data­base provides a large, un­en­cap­su­lated data struc­ture, which makes it much harder to do this. File Trans­fer allows an ap­plic­a­tion to react to changes as it pro­cesses the file, but the pro­cess is delayed.

                                                                                共享数据库具有未封装的数据这一事实也使得维护一系列集成应用程序变得更加困难。任何应用程序中的许多更改都可以触发数据库中的更改,并且数据库更改会对每个应用程序产生相当大的连锁反应。因此,使用共享数据库的组织通常非常不愿意更改数据库,这意味着应用程序开发工作对业务不断变化的需求的响应能力要差得多。

                                                                                The fact that Shared Data­base has un­en­cap­su­lated data also makes it more dif­fi­cult to main­tain a family of in­teg­rated ap­plic­a­tions. Many changes in any ap­plic­a­tion can trig­ger a change in the data­base, and data­base changes have a con­sid­er­able ripple effect through every ap­plic­a­tion. As a result, or­gan­iz­a­tions that use Shared Data­base are often very re­luct­ant to change the data­base, which means that the ap­plic­a­tion de­vel­op­ment work is much less re­spons­ive to the chan­ging needs of the busi­ness.

                                                                                需要一种机制,让一个应用程序调用另一个应用程序中的函数,传递需要共享的数据并调用告诉接收方应用程序如何处理数据的函数。

                                                                                What is needed is a mech­an­ism for one ap­plic­a­tion to invoke a func­tion in an­other ap­plic­a­tion, passing the data that needs to be shared and in­vok­ing the func­tion that tells the re­ceiver ap­plic­a­tion how to pro­cess the data.

                                                                                将每个应用程序开发为具有封装数据的大型对象或组件。提供接口以允许其他应用程序与正在运行的应用程序进行交互。

                                                                                De­velop each ap­plic­a­tion as a large-scale object or com­pon­ent with en­cap­su­lated data. Provide an in­ter­face to allow other ap­plic­a­tions to in­ter­act with the run­ning ap­plic­a­tion.

                                                                                图形/02inf03.gif



                                                                                远程过程调用将封装原则应用于集成应用程序。如果一个应用程序需要另一个应用程序拥有的某些信息,它会直接询问该应用程序。如果一个应用程序需要修改另一个应用程序的数据,它可以通过调用另一个应用程序来实现。这允许每个应用程序维护其拥有的数据的完整性。此外,每个应用程序都可以更改其内部数据的格式,而不会影响其他所有应用程序。

                                                                                Remote Pro­ced­ure In­voc­a­tion ap­plies the prin­ciple of en­cap­su­la­tion to in­teg­rat­ing ap­plic­a­tions. If an ap­plic­a­tion needs some in­form­a­tion that is owned by an­other ap­plic­a­tion, it asks that ap­plic­a­tion dir­ectly. If one ap­plic­a­tion needs to modify the data of an­other, it does so by making a call to the other ap­plic­a­tion. This allows each ap­plic­a­tion to main­tain the in­teg­rity of the data it owns. Fur­ther­more, each ap­plic­a­tion can alter the format of its in­ternal data without af­fect­ing every other ap­plic­a­tion.

                                                                                许多技术(例如 CORBA、COM、.NET Remoting 和 Java RMI)都实现了远程过程调用(也称为远程过程调用或 RPC)。这些方法因支持它们的系统数量及其易用性而异。这些环境通常会添加额外的功能,例如事务。由于其普遍性,当前最受欢迎的是使用 SOAP 和 XML 等标准的 Web 服务。Web 服务的一个特别有价值的特性是它们可以轻松地与 HTTP 配合使用,而 HTTP 很容易通过防火墙。

                                                                                A number of tech­no­lo­gies, such as CORBA, COM, .NET Re­mot­ing, and Java RMI, im­ple­ment Remote Pro­ced­ure In­voc­a­tion (also re­ferred to as Remote Pro­ced­ure Call, or RPC). These ap­proaches vary as to how many sys­tems sup­port them and their ease of use. Often these en­vir­on­ments add ad­di­tional cap­ab­il­it­ies, such as trans­ac­tions. For sheer ubi­quity, the cur­rent fa­vor­ite is Web ser­vices, using stand­ards such as SOAP and XML. A par­tic­u­larly valu­able fea­ture of Web ser­vices is that they work easily with HTTP, which is easy to get through fire­walls.

                                                                                事实上,有一些方法可以包装数据,这使得处理语义不一致变得更加容易。应用程序可以为相同的数据提供多个接口,允许某些客户端看到一种样式,而其他客户端则看到不同的样式。即使更新也可以使用多个接口。与关系视图相比,这提供了更多支持多种观点的能力。然而,集成商添加转换组件很困难,因此每个应用程序都必须与其邻居协商其接口。

                                                                                The fact that there are meth­ods that wrap the data makes it easier to deal with se­mantic dis­son­ance. Ap­plic­a­tions can provide mul­tiple in­ter­faces to the same data, al­low­ing some cli­ents to see one style and others a dif­fer­ent style. Even up­dates can use mul­tiple in­ter­faces. This provides a lot more abil­ity to sup­port mul­tiple points of view than can be achieved by re­la­tional views. How­ever, it is awk­ward for in­teg­rat­ors to add trans­form­a­tion com­pon­ents, so each ap­plic­a­tion has to ne­go­ti­ate its in­ter­face with its neigh­bors.

                                                                                由于软件开发人员习惯于过程调用,因此远程过程调用非常适合他们已经习惯的内容。事实上,这与其说是优点,不如说是缺点。远程和本地过程调用在性能和可靠性方面存在很大差异。如果人们不理解这些,那么远程过程调用可能会导致系统缓慢且不可靠(请参阅 [Waldo ]、[ EAA ]) 。

                                                                                Since soft­ware de­ve­lopers are used to pro­ced­ure calls, Remote Pro­ced­ure In­voc­a­tion fits in nicely with what they are already used to. Ac­tu­ally, this is more of a dis­ad­vant­age than an ad­vant­age. There are big dif­fer­ences in per­form­ance and re­li­ab­il­ity between remote and local pro­ced­ure calls. If people don't un­der­stand these, then Remote Pro­ced­ure In­voc­a­tion can lead to slow and un­re­li­able sys­tems (see [Waldo], [EAA]).

                                                                                尽管封装通过消除大型共享数据结构来帮助减少应用程序的耦合,但应用程序仍然相当紧密地耦合在一起。每个系统支持的远程调用往往会将不同的系统打成一个越来越大的结。特别是,按特定顺序执行某些操作可能会导致独立更改系统变得困难。之所以会出现这些类型的问题,是因为在单个应用程序中并不重要的问题在集成应用程序时变得非常重要。人们经常以设计单个应用程序的方式来设计集成,却没有意识到参与规则发生了巨大的变化。

                                                                                Al­though en­cap­su­la­tion helps reduce the coup­ling of the ap­plic­a­tions by elim­in­at­ing a large shared data struc­ture, the ap­plic­a­tions are still fairly tightly coupled to­gether. The remote calls that each system sup­ports tend to tie the dif­fer­ent sys­tems into a grow­ing knot. In par­tic­u­lar, se­quen­cing­do­ing cer­tain things in a par­tic­u­lar or­der­can make it dif­fi­cult to change sys­tems in­de­pend­ently. These types of prob­lems often arise be­cause issues that aren't sig­ni­fic­ant within a single ap­plic­a­tion become so when in­teg­rat­ing ap­plic­a­tions. People often design the in­teg­ra­tion the way they would design a single ap­plic­a­tion, un­aware that the rules of the en­gage­ment change dra­mat­ic­ally.

                                                                                要以更松散耦合的异步方式集成应用程序,请使用消息传递来实现少量数据的频繁交换,这些数据可能用于调用远程功能。

                                                                                To in­teg­rate ap­plic­a­tions in a more loosely coupled, asyn­chron­ous fash­ion, use Mes­saging to enable fre­quent ex­changes of small amounts of data, ones that are per­haps used to invoke remote func­tion­al­ity.

                                                                                  消息传递

                                                                                  Messaging

                                                                                  图形/messaging_icon.gif

                                                                                  一个企业有多个独立构建的应用程序,这些应用程序使用不同的语言和平台。企业需要以响应方式共享数据和流程。

                                                                                  An en­ter­prise has mul­tiple ap­plic­a­tions that are being built in­de­pend­ently, with dif­fer­ent lan­guages and plat­forms. The en­ter­prise needs to share data and pro­cesses in a re­spons­ive way.

                                                                                  如何集成多个应用程序以便它们协同工作并交换信息?

                                                                                  How can I in­teg­rate mul­tiple ap­plic­a­tions so that they work to­gether and can ex­change in­form­a­tion?



                                                                                  文件传输共享数据库使远程过程调用使通常,集成的挑战在于尽可能及时地在不同的系统之间进行协作,而不是将系统耦合在一起,以免它们在应用程序执行或应用程序开发方面变得不可靠。

                                                                                  File Trans­fer and Shared Data­base enable ap­plic­a­tions to share their data but not their func­tion­al­ity. Remote Pro­ced­ure In­voc­a­tion en­ables ap­plic­a­tions to share func­tion­al­ity, but it tightly couples them as well. Often the chal­lenge of in­teg­ra­tion is about making col­lab­or­a­tion between sep­ar­ate sys­tems as timely as pos­sible, without coup­ling sys­tems to­gether in such a way that they become un­re­li­able either in terms of ap­plic­a­tion ex­e­cu­tion or ap­plic­a­tion de­vel­op­ment.

                                                                                  文件传输允许系统无法跟上彼此的步伐。协作行为太慢了。共享数据库以响应方式将数据保存它也无法处理协作行为。

                                                                                  File Trans­fer allows you to keep the ap­plic­a­tions well de­coupled but at the cost of timeli­ness. Sys­tems just can't keep up with each other. Col­lab­or­at­ive be­ha­vior is way too slow. Shared Data­base keeps data to­gether in a re­spons­ive way but at the cost of coup­ling everything to the data­base. It also fails to handle col­lab­or­at­ive be­ha­vior.

                                                                                  面对这些问题,远程过程调用似乎是一个有吸引力的选择。但是将单一应用程序模型扩展到应用程序集成会弥补许多其他弱点。这些弱点始于分布式开发的本质问题。尽管 RPC 看起来像本地调用,但它们的行为方式并不相同。远程调用速度较慢,而且更有可能失败。当多个应用程序在企业中进行通信时,您不希望一个应用程序发生故障而导致所有其他应用程序瘫痪。此外,您不想设计一个假设调用速度很快的系统,并且您不希望每个应用程序了解其他应用程序的详细信息,

                                                                                  Faced with these prob­lems, Remote Pro­ced­ure In­voc­a­tion seems an ap­peal­ing choice. But ex­tend­ing a single ap­plic­a­tion model to ap­plic­a­tion in­teg­ra­tion dredges up plenty of other weak­nesses. These weak­nesses start with the es­sen­tial prob­lems of dis­trib­uted de­vel­op­ment. Des­pite that RPCs look like local calls, they don't behave the same way. Remote calls are slower, and they are much more likely to fail. With mul­tiple ap­plic­a­tions com­mu­nic­at­ing across an en­ter­prise, you don't want one ap­plic­a­tion's fail­ure to bring down all of the other ap­plic­a­tions. Also, you don't want to design a system as­sum­ing that calls are fast, and you don't want each ap­plic­a­tion know­ing the de­tails about other ap­plic­a­tions, even if it's only de­tails about their in­ter­faces.

                                                                                  我们需要的是像文件传输这样的东西,其中可以快速生成大量小数据包并轻松传输,并且当有新数据包可供使用时,接收器应用程序会自动收到通知。传输需要重试机制以确保其成功。用于存储数据的任何磁盘结构或数据库的详细信息都需要对应用程序隐藏,以便与共享数据库不同,可以轻松更改存储架构和详细信息以反映企业不断变化的需求。一个应用程序应该能够将数据包发送到另一个应用程序以调用另一个应用程序中的行为,例如远程过程调用,但不易失败。数据传输应该是异步的,以便发送方不需要等待接收方,特别是在需要重试时。

                                                                                  What we need is some­thing like File Trans­fer in which lots of little data pack­ets can be pro­duced quickly and trans­ferred easily, and the re­ceiver ap­plic­a­tion is auto­mat­ic­ally no­ti­fied when a new packet is avail­able for con­sump­tion. The trans­fer needs a retry mech­an­ism to make sure it suc­ceeds. The de­tails of any disk struc­ture or data­base for stor­ing the data needs to be hidden from the ap­plic­a­tions so that, unlike Shared Data­base, the stor­age schema and de­tails can be easily changed to re­flect the chan­ging needs of the en­ter­prise. One ap­plic­a­tion should be able to send a packet of data to an­other ap­plic­a­tion to invoke be­ha­vior in the other ap­plic­a­tion, like Remote Pro­ced­ure In­voc­a­tion, but without being prone to fail­ure. The data trans­fer should be asyn­chron­ous so that the sender does not need to wait on the re­ceiver, es­pe­cially when retry is ne­ces­sary.

                                                                                  使用消息传递以可定制的格式频繁、立即、可靠、异步地传输数据包。

                                                                                  Use Mes­saging to trans­fer pack­ets of data fre­quently, im­me­di­ately, re­li­ably, and asyn­chron­ously, using cus­tom­iz­able formats.

                                                                                  图形/02inf04.gif



                                                                                  异步消息传递从根本上来说是对分布式系统问题的务实反应。发送消息不需要两个系统同时启动并准备就绪。此外,以异步方式思考通信迫使开发人员认识到使用远程应用程序的速度较慢,这鼓励设计具有高内聚性(大量本地工作)和低粘附性(远程选择性工作)的组件。

                                                                                  Asyn­chron­ous mes­saging is fun­da­ment­ally a prag­matic re­ac­tion to the prob­lems of dis­trib­uted sys­tems. Send­ing a mes­sage does not re­quire both sys­tems to be up and ready at the same time. Fur­ther­more, think­ing about the com­mu­nic­a­tion in an asyn­chron­ous manner forces de­ve­lopers to re­cog­nize that work­ing with a remote ap­plic­a­tion is slower, which en­cour­ages design of com­pon­ents with high co­he­sion (lots of work loc­ally) and low ad­he­sion (se­lect­ive work re­motely).

                                                                                  消息传递系统还允许在使用文件传输时实现大部分解耦。 消息可以在传输过程中进行转换,而发送者或接收者都不知道该转换。这种解耦允许集成商在向多个接收器广播消息、将消息路由到多个接收器之一或其他拓扑之间进行选择。这将集成决策与应用程序开发分开。由于人类问题往往将应用程序开发与应用程序集成分开,因此这种方法符合人性而不是违背人性。

                                                                                  Mes­saging sys­tems also allow much of the de­coup­ling you get when using File Trans­fer. Mes­sages can be trans­formed in transit without either the sender or re­ceiver know­ing about the trans­form­a­tion. The de­coup­ling allows in­teg­rat­ors to choose between broad­cast­ing mes­sages to mul­tiple re­ceiv­ers, rout­ing a mes­sage to one of many re­ceiv­ers, or other to­po­lo­gies. This sep­ar­ates in­teg­ra­tion de­cisions from the de­vel­op­ment of the ap­plic­a­tions. Since human issues tend to sep­ar­ate ap­plic­a­tion de­vel­op­ment from ap­plic­a­tion in­teg­ra­tion, this ap­proach works with human nature rather than against it.

                                                                                  这种转换意味着不同的应用程序可以具有完全不同的概念模型。当然,这意味着会出现语义不一致。然而,消息传递的观点是,共享数据库用于避免语义不一致的措施过于复杂,无法在实践中发挥作用。此外,第三方应用程序以及作为公司合并的一部分添加的应用程序也会出现语义不一致,因此消息传递方法是解决该问题,而不是设计应用程序来避免该问题。

                                                                                  The trans­form­a­tion means that sep­ar­ate ap­plic­a­tions can have quite dif­fer­ent con­cep­tual models. Of course, this means that se­mantic dis­son­ance will occur. How­ever, the mes­saging view­point is that the meas­ures used by Shared Data­base to avoid se­mantic dis­son­ance are too com­plic­ated to work in prac­tice. Also, se­mantic dis­son­ance is going to occur with third-party ap­plic­a­tions and with ap­plic­a­tions added as part of a cor­por­ate merger, so the mes­saging ap­proach is to ad­dress the issue rather than design ap­plic­a­tions to avoid it.

                                                                                  通过频繁发送小消息,您还允许应用程序进行行为协作以及共享数据。如果收到保险索赔后需要启动流程,则可以通过在收到单个索赔时发送消息来立即完成。可以请求信息并快速做出答复。虽然这种协作不会像远程过程调用那么快,但在处理消息和返回响应时,调用者不需要停止。而且消息传递并不像许多人想象的那么慢许多消息传递解决方案起源于金融服务行业,该行业每秒有数以千计的股票报价或交易必须通过消息传递系统。

                                                                                  By send­ing small mes­sages fre­quently, you also allow ap­plic­a­tions to col­lab­or­ate be­ha­vi­or­ally as well as share data. If a pro­cess needs to be launched once an in­sur­ance claim is re­ceived, it can be done im­me­di­ately by send­ing a mes­sage when a single claim comes in. In­form­a­tion can be re­ques­ted and a reply made rap­idly. While such col­lab­or­a­tion isn't going to be as fast as Remote Pro­ced­ure In­voc­a­tion, the caller needn't stop while the mes­sage is being pro­cessed and the re­sponse re­turned. And mes­saging isn't as slow as many people think­many mes­saging solu­tions ori­gin­ated in the fin­an­cial ser­vices in­dustry where thou­sands of stock quotes or trades have to pass through a mes­saging system every second.

                                                                                  本书是关于消息传递的,因此您可以放心地假设我们认为消息传递通常是企业应用程序集成的最佳方法。但是,您不应该假设它没有问题。消息传递中消息的高频率减少了困扰文件传输的许多不一致问题,但它并没有完全删除它们。由于系统更新不完全同步,仍然会存在一些滞后问题。异步设计并不是大多数软件人员所接受的教学方式,因此存在许多不同的规则和技术。消息传递上下文使得这比在 X Windows 等异步应用程序环境中编程要容易一些,但异步仍然有一个学习曲线。在这种环境中测试和调试也更加困难。

                                                                                  This book is about Mes­saging, so you can safely assume that we con­sider Mes­saging to be gen­er­ally the best ap­proach to en­ter­prise ap­plic­a­tion in­teg­ra­tion. You should not assume, how­ever, that it is free of prob­lems. The high fre­quency of mes­sages in Mes­saging re­duces many of the in­con­sist­ency prob­lems that be­devil File Trans­fer, but it doesn't remove them en­tirely. There are still going to be some lag prob­lems with sys­tems not being up­dated quite sim­ul­tan­eously. Asyn­chron­ous design is not the way most soft­ware people are taught, and as a result there are many dif­fer­ent rules and tech­niques in place. The mes­saging con­text makes this a bit easier than pro­gram­ming in an asyn­chron­ous ap­plic­a­tion en­vir­on­ment like X Win­dows, but asyn­chrony still has a learn­ing curve. Test­ing and de­bug­ging are also harder in this en­vir­on­ment.

                                                                                  转换消息的能力有一个很好的好处,即允许应用程序彼此之间比远程过程调用更加解耦。但这种独立性确实意味着集成商通常需要编写大量混乱的粘合代码来将所有内容组合在一起。

                                                                                  The abil­ity to trans­form mes­sages has the nice be­ne­fit of al­low­ing ap­plic­a­tions to be much more de­coupled from each other than in Remote Pro­ced­ure In­voc­a­tion. But this in­de­pend­ence does mean that in­teg­rat­ors are often left with writ­ing a lot of messy glue code to fit everything to­gether.

                                                                                  一旦您决定要使用消息传递进行系统集成,就有许多新问题需要考虑和可以采用的实践。

                                                                                  Once you decide that you want to use Mes­saging for system in­teg­ra­tion, there are a number of new issues to con­sider and prac­tices you can employ.

                                                                                  如何传输数据包?

                                                                                  How do you trans­fer pack­ets of data?

                                                                                  发送方通过连接发送方和接收方的消息通道发送消息,将数据发送到接收方。

                                                                                  A sender sends data to a re­ceiver by send­ing a Mes­sage via a Mes­sage Chan­nel that con­nects the sender and re­ceiver.

                                                                                  您如何知道将数据发送到哪里?

                                                                                  How do you know where to send the data?

                                                                                  如果发送方不知道在哪里寻址数据,它可以将数据发送到消息路由器,消息路由器会将数据定向到正确的接收方。

                                                                                  If the sender does not know where to ad­dress the data, it can send the data to a Mes­sage Router, which will direct the data to the proper re­ceiver.

                                                                                  您如何知道要使用什么数据格式?

                                                                                  How do you know what data format to use?

                                                                                  如果发送方和接收方对数据格式不一致,则发送方可以将数据定向到消息转换器,将数据转换为接收方的格式,然后将数据转发到接收方。

                                                                                  If the sender and re­ceiver do not agree on the data format, the sender can direct the data to a Mes­sage Trans­lator that will con­vert the data to the re­ceiver's format and then for­ward the data to the re­ceiver.

                                                                                  如果您是应用程序开发人员,如何将应用程序连接到消息传递系统?

                                                                                  If you're an ap­plic­a­tion de­ve­loper, how do you con­nect your ap­plic­a­tion to the mes­saging system?

                                                                                  希望使用消息传递的应用程序将实现消息端点来执行实际的发送和接收。

                                                                                  An ap­plic­a­tion that wishes to use mes­saging will im­ple­ment Mes­sage En­d­points to per­form the actual send­ing and re­ceiv­ing.

                                                                                    介绍

                                                                                    Introduction

                                                                                    第 2 章“集成样式”中,我们讨论了应用程序相互连接的各种选项,包括消息传递。消息传递通过异步通信使应用程序松散耦合,这也使通信更加可靠,因为两个应用程序不必同时运行。消息传递使消息传递系统负责将数据从一个应用程序传输到另一个应用程序,因此应用程序可以专注于需要共享哪些数据,而不是如何共享数据。

                                                                                    In Chapter 2, "In­teg­ra­tion Styles," we dis­cussed the vari­ous op­tions for con­nect­ing ap­plic­a­tions with one an­other, in­clud­ing Mes­saging. Mes­saging makes ap­plic­a­tions loosely coupled by com­mu­nic­at­ing asyn­chron­ously, which also makes the com­mu­nic­a­tion more re­li­able be­cause the two ap­plic­a­tions do not have to be run­ning at the same time. Mes­saging makes the mes­saging system re­spons­ible for trans­fer­ring data from one ap­plic­a­tion to an­other, so the ap­plic­a­tions can focus on what data they need to share as op­posed to how to share it.

                                                                                    基本消息传递概念

                                                                                    Basic Mes­saging Con­cepts

                                                                                    与大多数技术一样,消息传递涉及某些基本概念。一旦理解了这些概念,您甚至可以在了解有关如何使用该技术的所有细节之前就理解该技术。以下是基本消息传递概念。

                                                                                    Like most tech­no­lo­gies, Mes­saging in­volves cer­tain basic con­cepts. Once you un­der­stand these con­cepts, you can make sense of the tech­no­logy even before you un­der­stand all of the de­tails about how to use it. The fol­low­ing are the basic mes­saging con­cepts.

                                                                                    通道 消息传递应用程序通过消息通道(将发送方连接到接收方的虚拟管道)传输数据。新安装的消息系统通常不包含任何通道;您必须确定您的应用程序需要如何进行通信,然后创建通道来促进它。

                                                                                    消息 消息 是可以在通道上传输的原子数据包。因此,要传输数据,应用程序必须将数据分成一个或多个数据包,将每个数据包包装为一条消息,然后在通道上发送该消息。同样,接收者应用程序接收消息并且必须从消息中提取数据来处理它。消息系统将重复尝试传递消息(例如,将其从发送者传输到接收者),直到成功为止。

                                                                                    管道和过滤器 在 最简单的情况下,消息传递系统将消息直接从发送者的计算机传递到接收者的计算机。然而,在消息由原始发送者发送之后但在最终接收者接收之前,通常需要对消息执行某些操作。例如,由于接收方期望的消息格式与发送方的消息格式不同,因此可能必须验证或转换消息。管道和过滤器架构描述了如何使用通道将多个处理步骤链接在一起。

                                                                                    路由 在 具有众多应用程序和连接它们的通道的大型企业中,消息可能必须经过多个通道才能到达其最终目的地。消息必须遵循的路线可能非常复杂,以至于原始发送者不知道哪个通道会将消息发送给最终接收者。相反,原始发送者将消息发送到消息路由器,这是一个应用程序组件,它取代了管道和过滤器架构中的过滤器。然后,路由器确定如何导航通道拓扑并将消息定向到最终接收者,或至少定向到下一个路由器。

                                                                                    转换 不同的应用程序可能不会就相同概念数据的格式达成一致;发送方以一种方式格式化消息,但接收方希望以另一种方式格式化消息。为了协调这一点,消息必须经过中间过滤器,即消息转换器,它将消息从一种格式转换为另一种格式。

                                                                                    端点 大多数 应用程序没有任何与消息传递系统交互的内置功能。相反,它们必须包含一层代码,该代码层既知道应用程序如何工作,又知道消息传递系统如何工作,从而将两者联系起来,以便它们协同工作。该桥接代码是一组协调的消息端点,使应用程序能够发送和接收消息。

                                                                                    Chan­nels Mes­saging ap­plic­a­tions trans­mit data through a Mes­sage Chan­nel, a vir­tual pipe that con­nects a sender to a re­ceiver. A newly in­stalled mes­saging system typ­ic­ally doesn't con­tain any chan­nels; you must de­term­ine how your ap­plic­a­tions need to com­mu­nic­ate and then create the chan­nels to fa­cil­it­ate it.

                                                                                    Mes­sages A Mes­sage is an atomic packet of data that can be trans­mit­ted on a chan­nel. Thus, to trans­mit data, an ap­plic­a­tion must break the data into one or more pack­ets, wrap each packet as a mes­sage, and then send the mes­sage on a chan­nel. Like­wise, a re­ceiver ap­plic­a­tion re­ceives a mes­sage and must ex­tract the data from the mes­sage to pro­cess it. The mes­sage system will try re­peatedly to de­liver the mes­sage (e.g., trans­mit it from the sender to the re­ceiver) until it suc­ceeds.

                                                                                    Pipes and Fil­ters In the simplest case, the mes­saging system de­liv­ers a mes­sage dir­ectly from the sender's com­puter to the re­ceiver's com­puter. How­ever, cer­tain ac­tions often need to be per­formed on the mes­sage after it is sent by its ori­ginal sender but before it is re­ceived by its final re­ceiver. For ex­ample, the mes­sage may have to be val­id­ated or trans­formed be­cause the re­ceiver ex­pects a mes­sage format dif­fer­ent from the sender's. The Pipes and Fil­ters ar­chi­tec­ture de­scribes how mul­tiple pro­cess­ing steps can be chained to­gether using chan­nels.

                                                                                    Rout­ing In a large en­ter­prise with nu­mer­ous ap­plic­a­tions and chan­nels to con­nect them, a mes­sage may have to go through sev­eral chan­nels to reach its final des­tin­a­tion. The route a mes­sage must follow may be so com­plex that the ori­ginal sender does not know what chan­nel will get the mes­sage to the final re­ceiver. In­stead, the ori­ginal sender sends the mes­sage to a Mes­sage Router, an ap­plic­a­tion com­pon­ent that takes the place of a filter in the Pipes and Fil­ters ar­chi­tec­ture. The router then de­term­ines how to nav­ig­ate the chan­nel to­po­logy and dir­ects the mes­sage to the final re­ceiver, or at least to the next router.

                                                                                    Trans­form­a­tion Vari­ous ap­plic­a­tions may not agree on the format for the same con­cep­tual data; the sender formats the mes­sage one way, but the re­ceiver ex­pects it to be format­ted an­other way. To re­con­cile this, the mes­sage must go through an in­ter­me­di­ate filter, a Mes­sage Trans­lator, which con­verts the mes­sage from one format to an­other.

                                                                                    En­d­points Most ap­plic­a­tions do not have any built-in cap­ab­il­ity to in­ter­face with a mes­saging system. Rather, they must con­tain a layer of code that knows both how the ap­plic­a­tion works and how the mes­saging system works, bridging the two so that they work to­gether. This bridge code is a set of co­ordin­ated Mes­sage En­d­points that enable the ap­plic­a­tion to send and re­ceive mes­sages.

                                                                                    图书组织

                                                                                    Book Or­gan­iz­a­tion

                                                                                    本章中的模式为您提供了如何使用消息传递实现企业集成的基本词汇和理解。后续的每一章都建立在本章的基本模式之一之上,并更深入地涵盖该特定主题。

                                                                                    The pat­terns in this chapter provide you with the basic vocab­u­lary and un­der­stand­ing of how to achieve en­ter­prise in­teg­ra­tion using Mes­saging. Each sub­se­quent chapter builds on one of the base pat­terns in this chapter and covers that par­tic­u­lar topic in more depth.

                                                                                    根模式和章节的关系

                                                                                    Re­la­tion­ship of Root Pat­terns and Chapters

                                                                                    图形/03inf01.gif

                                                                                    您可以直接阅读本章,以了解消息传递中主要主题的概述。有关任一主题的更多详细信息,请跳至与该特定模式相关的章节。

                                                                                    You can read this chapter straight through for an over­view of the main topics in Mes­saging. For more de­tails about any one of these topics, skip ahead to the chapter as­so­ci­ated with that par­tic­u­lar pat­tern.

                                                                                      留言通道

                                                                                      Message Channel

                                                                                      图形/messagechannel_icon.gif

                                                                                      企业有两个独立的应用程序需要使用消息传递进行通信

                                                                                      An en­ter­prise has two sep­ar­ate ap­plic­a­tions that need to com­mu­nic­ate by using Mes­saging.

                                                                                      一个应用程序如何使用消息传递与另一个应用程序进行通信?

                                                                                      How does one ap­plic­a­tion com­mu­nic­ate with an­other using mes­saging?



                                                                                      一旦一组应用程序具有可用的消息传递系统,人们很容易认为任何应用程序都可以在您希望的任何时候与任何其他应用程序进行通信。然而,消息传递系统并不能神奇地连接所有应用程序。

                                                                                      Once a group of ap­plic­a­tions has a mes­saging system avail­able, it's tempt­ing to think that any ap­plic­a­tion can com­mu­nic­ate with any other ap­plic­a­tion any­time you want it to. Yet, the mes­saging system does not ma­gic­ally con­nect all of the ap­plic­a­tions.

                                                                                      应用程序神奇地连接

                                                                                      Ap­plic­a­tions Ma­gic­ally Con­nec­ted

                                                                                      图形/03inf02.gif

                                                                                      同样,应用程序并不只是随机地将信息扔到消息传递系统中,而其他应用程序只是随机抓取它们遇到的任何信息。(即使这有效,效率也不会很高。)相反,发送信息的应用程序知道它是什么类型的信息,并且想要接收信息的应用程序不仅仅寻找任何信息,而是寻找任何信息。他们可以使用的特定类型的信息。因此,消息传递系统并不是一个供应用程序将信息放入或从中取出信息的大桶。它是一组连接,使应用程序能够通过以预定的、可预测的方式传输信息来进行通信。

                                                                                      Like­wise, it's not as though an ap­plic­a­tion just ran­domly throws out in­form­a­tion into the mes­saging system while other ap­plic­a­tions just ran­domly grab whatever in­form­a­tion they run across. (Even if this worked, it wouldn't be very ef­fi­cient.) Rather, the ap­plic­a­tion send­ing out the in­form­a­tion knows what sort of in­form­a­tion it is, and the ap­plic­a­tions that would like to re­ceive in­form­a­tion aren't look­ing for just any in­form­a­tion but for par­tic­u­lar types of in­form­a­tion they can use. So the mes­saging system isn't a big bucket that ap­plic­a­tions throw in­form­a­tion into and pull in­form­a­tion out of. It's a set of con­nec­tions that en­ables ap­plic­a­tions to com­mu­nic­ate by trans­mit­ting in­form­a­tion in pre­de­ter­mined, pre­dict­able ways.

                                                                                      使用消息通道连接应用程序其中一个应用程序将信息写入通道,另一个应用程序从通道读取该信息。

                                                                                      Con­nect the ap­plic­a­tions using a Mes­sage Chan­nel, where one ap­plic­a­tion writes in­form­a­tion to the chan­nel and the other one reads that in­form­a­tion from the chan­nel.

                                                                                      图形/03inf03.gif



                                                                                      当应用程序有信息需要通信时,它不仅仅将信息扔到消息传递系统中,而是将信息添加到特定的消息通道中。接收信息的应用程序并不只是从消息传递系统中随机获取信息;而是从消息传递系统中随机获取信息。它从特定的消息通道检索信息。

                                                                                      When an ap­plic­a­tion has in­form­a­tion to com­mu­nic­ate, it doesn't just fling the in­form­a­tion into the mes­saging system but adds the in­form­a­tion to a par­tic­u­lar Mes­sage Chan­nel. An ap­plic­a­tion re­ceiv­ing in­form­a­tion doesn't just pick it up at random from the mes­saging system; it re­trieves the in­form­a­tion from a par­tic­u­lar Mes­sage Chan­nel.

                                                                                      发送信息的应用程序不一定知道哪个特定应用程序最终将检索该信息,但可以确保检索该信息的应用程序对该信息感兴趣。这是因为消息系统有不同的消息通道用于应用程序想要通信的不同类型的信息。当应用程序发送信息时,它不会随机地将信息添加到任何可用的通道;它将其添加到一个通道,其特定目的是传达此类信息。同样,想要接收特定信息的应用程序不会从某个随机通道获取信息;而是会从某些随机通道中获取信息。它根据需要的信息类型选择从哪个渠道获取信息。

                                                                                      The ap­plic­a­tion send­ing in­form­a­tion doesn't ne­ces­sar­ily know what par­tic­u­lar ap­plic­a­tion will end up re­triev­ing it, but it can be as­sured that the ap­plic­a­tion that re­trieves the in­form­a­tion is in­ter­ested in the in­form­a­tion. This is be­cause the mes­saging system has dif­fer­ent Mes­sage Chan­nels for dif­fer­ent types of in­form­a­tion the ap­plic­a­tions want to com­mu­nic­ate. When an ap­plic­a­tion sends in­form­a­tion, it doesn't ran­domly add the in­form­a­tion to any chan­nel avail­able; it adds it to a chan­nel whose spe­cific pur­pose is to com­mu­nic­ate that sort of in­form­a­tion. Like­wise, an ap­plic­a­tion that wants to re­ceive par­tic­u­lar in­form­a­tion doesn't pull info off some random chan­nel; it se­lects what chan­nel to get in­form­a­tion from based on what type of in­form­a­tion it wants.

                                                                                      通道是消息系统中的逻辑地址。它们的实际实现方式取决于消息传递系统产品及其实现。也许每个消息端点都与每个其他端点有直接连接,或者它们可能都通过中央集线器连接。也许几个单独的逻辑通道被配置为一个物理通道,但仍然可以保持哪些消息要发送到哪个目的地。定义的逻辑通道集对应用程序隐藏了这些配置详细信息。

                                                                                      Chan­nels are lo­gical ad­dresses in the mes­saging system. How they're ac­tu­ally im­ple­men­ted de­pends on the mes­saging system product and its im­ple­ment­a­tion. Per­haps every Mes­sage En­d­point has a direct con­nec­tion to every other en­d­point, or per­haps they're all con­nec­ted through a cent­ral hub. Per­haps sev­eral sep­ar­ate lo­gical chan­nels are con­figured as one phys­ical chan­nel that nev­er­the­less keeps straight which mes­sages are in­ten­ded for which des­tin­a­tion. The set of defined lo­gical chan­nels hides these con­fig­ur­a­tion de­tails from the ap­plic­a­tions.

                                                                                      消息传递系统不会自动预先配置应用程序通信所需的所有消息通道。相反,设计应用程序和应用程序之间通信的开发人员必须决定他们需要什么渠道进行通信。然后,安装消息系统软件的系统管理员还必须对其进行配置,以设置应用程序期望的通道。尽管某些消息传递系统实现支持在应用程序运行时创建新通道,但这并不是很有用,因为除了创建通道的应用程序之外,其他应用程序也必须了解新通道,以便它们也可以开始使用它。因此,可用信道的数量和用途往往在部署时是固定的。第 4 章,“消息传递渠道。”)

                                                                                      A mes­saging system doesn't auto­mat­ic­ally come pre­con­figured with all of the mes­sage chan­nels the ap­plic­a­tions need to com­mu­nic­ate. Rather, the de­ve­lopers design­ing the ap­plic­a­tions and the com­mu­nic­a­tion between them have to decide what chan­nels they need for the com­mu­nic­a­tion. Then the system ad­min­is­trator who in­stalls the mes­saging system soft­ware must also con­fig­ure it to set up the chan­nels that the ap­plic­a­tions expect. Al­though some mes­saging system im­ple­ment­a­tions sup­port cre­at­ing new chan­nels while the ap­plic­a­tions are run­ning, this isn't very useful be­cause other ap­plic­a­tions be­sides the one that cre­ates the chan­nel must know about the new chan­nel so they can start using it too. Thus, the number and pur­pose of chan­nels avail­able tend to be fixed at de­ploy­ment time. (There are ex­cep­tions to this rule; see the in­tro­duc­tion to Chapter 4, "Mes­saging Chan­nels.")

                                                                                      一些消息传递词汇

                                                                                      A Little Bit of Messaging Vocabulary

                                                                                      那么我们如何称呼通过消息通道进行通信的应用程序呢?有许多术语基本上是相同的。最通用的术语可能是发送者接收者;应用程序将消息发送到消息通道以由另一个应用程序接收。其他流行的术语是生产者和消费者。 您还会看到发布者订阅者,但它们更适合发布-订阅通道,并且通常以通用形式使用。有时我们说应用程序监听在另一个应用程序与之通信的通道上在 Web 服务领域,我们通常谈论请求者提供者。这些术语通常意味着请求者向提供者发送消息并接收返回的响应。在过去,我们将这些称为客户端服务器(这些术语是等效的,但说“客户端”和“服务器”并不酷)。

                                                                                      So what do we call the ap­plic­a­tions that com­mu­nic­ate via a Mes­sage Chan­nel? There are a number of terms out there that are largely equi­val­ent. The most gen­eric terms are prob­ably sender and re­ceiver; an ap­plic­a­tion sends a mes­sage to a Mes­sage Chan­nel to be re­ceived by an­other ap­plic­a­tion. Other pop­u­lar terms are pro­du­cer and con­sumer. You will also see pub­lisher and sub­scriber, but they are geared more toward Pub­lish-Sub­scribe Chan­nels and are often used in gen­eric form. Some­times we say that an ap­plic­a­tion listens on a chan­nel to which an­other ap­plic­a­tion talks. In the world of Web ser­vices, we gen­er­ally talk about a re­quester and a pro­vider. These terms usu­ally imply that the re­quester sends a mes­sage to the pro­vider and re­ceives a re­sponse back. In the olden days we called these client and server (the terms are equi­val­ent, but saying "client" and "server" is not cool).

                                                                                      现在它变得令人困惑。在处理 Web 服务时,向服务提供者发送消息的应用程序通常被称为服务的使用者,即使它发送的是请求消息。我们可以这样想:消费者向提供者发送消息,然后消费响应。幸运的是,具有此含义的术语的使用仅限于远程过程调用场景。发送或接收消息的应用程序可以称为消息系统的客户端;更具体的术语是端点或消息端点。

                                                                                      Now it gets con­fus­ing. When deal­ing with Web ser­vices, the ap­plic­a­tion that sends a mes­sage to the ser­vice pro­vider is often re­ferred to as the con­sumer of the ser­vice even though it sends the re­quest mes­sage. We can think of it in such a way that the con­sumer sends a mes­sage to the pro­vider and then con­sumes the re­sponse. Luck­ily, use of the term with this mean­ing is lim­ited to Remote Pro­ced­ure In­voc­a­tion scen­arios. An ap­plic­a­tion that sends or re­ceives mes­sages may be called a client of the mes­saging system; a more spe­cific term is en­d­point or mes­sage en­d­point.



                                                                                      当开发人员第一次开始使用消息系统时,经常会愚弄他们的是创建通道到底需要做什么。开发人员可以编写调用JMS API 中定义的createQueue方法的 Java 代码或包含new MessageQueue 语句的 .NET 代码,但这代码实际上都不会在消息传递系统中分配新的队列资源。相反,这些代码片段只是实例化一个运行时对象,该对象提供对已使用其管理工具在消息传递系统中创建的资源的访问。

                                                                                      Some­thing that often fools de­ve­lopers when they first get star­ted with using a mes­saging system is what ex­actly needs to be done to create a chan­nel. A de­ve­loper can write Java code that calls the method cre­ateQueue defined in the JMS API or .NET code that in­cludes the state­ment new Mes­sageQueue, but neither code ac­tu­ally al­loc­ates a new queue re­source in the mes­saging system. Rather, these pieces of code simply in­stan­ti­ate a runtime object that provides access to a re­source that was already cre­ated in the mes­saging system using its ad­min­is­tra­tion tools.

                                                                                      在设计消息传递系统的通道时,您应该记住另一个问题:通道很便宜,但它们不是免费的。应用程序需要多个通道来传输不同类型的信息以及将相同的信息传输到许多其他应用程序。每个通道都需要内存来表示消息;持久通道也需要磁盘空间。即使企业系统具有无限的内存和磁盘空间,任何消息传递系统实施通常都会对其可以一致服务的通道数量施加一些硬性或实际限制。因此,在您的应用程序需要时计划创建新通道,但如果需要数千个通道或需要以可能需要数千个通道的方式进行扩展,

                                                                                      There is an­other issue you should keep in mind when design­ing the chan­nels for a mes­saging system: Chan­nels are cheap, but they're not free. Ap­plic­a­tions need mul­tiple chan­nels for trans­mit­ting dif­fer­ent types of in­form­a­tion and trans­mit­ting the same in­form­a­tion to lots of other ap­plic­a­tions. Each chan­nel re­quires memory to rep­res­ent the mes­sages; per­sist­ent chan­nels re­quire disk space as well. Even if an en­ter­prise system had un­lim­ited memory and disk space, any mes­saging system im­ple­ment­a­tion usu­ally im­poses some hard or prac­tical limit to how many chan­nels it can ser­vice con­sist­ently. So plan on cre­at­ing new chan­nels as your ap­plic­a­tion needs them, but if it needs thou­sands of chan­nels or needs to scale in ways that may re­quire thou­sands of chan­nels, you'll need to choose a highly scal­able mes­saging system im­ple­ment­a­tion and test that scalab­il­ity to make sure it meets your needs.

                                                                                      频道名称

                                                                                      Channel Names

                                                                                      如果通道是逻辑地址,那么这些地址是什么样的?与许多情况一样,详细的答案取决于消息传递系统的实现。然而,在大多数情况下,频道是通过字母数字名称引用的,例如MyChannel 。许多邮件系统支持分层通道命名方案,这使您能够以类似于具有文件夹和子文件夹的文件系统的方式组织通道。例如,MyCorp/Prod/OrderProcessing/NewOrders将指示在MyCorp 的生产应用程序中使用并包含新订单的通道。

                                                                                      If chan­nels are lo­gical ad­dresses, what do these ad­dresses look like? As in so many cases, the de­tailed answer de­pends on the im­ple­ment­a­tion of the mes­saging system. Nev­er­the­less, in most cases chan­nels are ref­er­enced by an al­pha­nu­meric name, such as My­Chan­nel. Many mes­saging sys­tems sup­port a hier­arch­ical chan­nel-naming scheme, which en­ables you to or­gan­ize chan­nels in a way that is sim­ilar to a file system with folders and sub­folders. For ex­ample, MyCorp/Prod/Or­der­Pro­cess­ing/Ne­wOrders would in­dic­ate a chan­nel that is used in a pro­duc­tion ap­plic­a­tion at MyCorp and con­tains new orders.



                                                                                      有两种不同类型的消息通道:点对点通道发布订阅通道。在同一通道上混合不同的数据类型可能会导致很多混乱;为了避免这种情况,请使用单独的数据类型通道选择性消费者使一个物理通道在逻辑上像多个通道一样运作。使用消息传递的应用程序通常受益于无效消息的特殊通道,即Invalid Message Channel 。希望使用消息传递但无权访问消息传递客户端的应用程序仍然可以使用通道适配器连接到消息传递系统。一组精心设计的通道形成了消息总线,其作用类似于整组应用程序的消息传递 API。

                                                                                      There are two dif­fer­ent kinds of mes­sage chan­nels: Point-to-Point Chan­nels and Pub­lish-Sub­scribe Chan­nels. Mixing dif­fer­ent data types on the same chan­nel can cause a lot of con­fu­sion; to avoid this, use sep­ar­ate Data­type Chan­nels. Se­lect­ive Con­sumer makes one phys­ical chan­nel act lo­gic­ally like mul­tiple chan­nels. Ap­plic­a­tions that use mes­saging often be­ne­fit from a spe­cial chan­nel for in­valid mes­sages, an In­valid Mes­sage Chan­nel. Ap­plic­a­tions that wish to use Mes­saging but do not have access to a mes­saging client can still con­nect to the mes­saging system using Chan­nel Ad­apters. A well-de­signed set of chan­nels forms a Mes­sage Bus that acts like a mes­saging API for a whole group of ap­plic­a­tions.

                                                                                      示例: 股票交易

                                                                                      Ex­ample: Stock Trad­ing

                                                                                      当股票交易应用程序进行交易时,它会将请求发送到交易请求的消息通道上。处理交易请求的另一个应用程序将寻找它可以在同一消息通道上处理的请求。如果请求应用程序需要请求股票报价,它可能会使用不同的消息通道(专为股票报价设计的消息通道),以便报价请求与交易请求保持分离。

                                                                                      When a stock trad­ing ap­plic­a­tion makes a trade, it puts the re­quest on a Mes­sage Chan­nel for trade re­quests. An­other ap­plic­a­tion that pro­cesses trade re­quests will look for those it can pro­cess on that same mes­sage chan­nel. If the re­quest­ing ap­plic­a­tion needs to re­quest a stock quote, it will prob­ably use a dif­fer­ent Mes­sage Chan­nel, one de­signed for stock quotes, so that the quote re­quests stay sep­ar­ate from the trade re­quests.



                                                                                      示例: J2EE JMS 参考实现

                                                                                      Ex­ample: J2EE JMS Ref­er­ence Im­ple­ment­a­tion

                                                                                      让我们看看如何在 JMS 中创建消息通道。J2EE SDK 附带了 J2EE 服务的参考实现,包括 JMS。可以使用j2ee命令启动参考服务器。必须使用j2eeadmin工具配置消息通道。该工具可以配置队列和主题。

                                                                                      Let's look at how to create a Mes­sage Chan­nel in JMS. The J2EE SDK ships with a ref­er­ence im­ple­ment­a­tion of the J2EE ser­vices, in­clud­ing JMS. The ref­er­ence server can be star­ted with the j2ee com­mand. Mes­sage chan­nels have to be con­figured using the j2eead­min tool. This tool can con­fig­ure both queues and topics.

                                                                                      j2eeadmin -addJmsDestination jms/mytopic 主题
                                                                                      j2eeadmin -addJmsDestination jms/myqueue 队列
                                                                                      
                                                                                      j2eead­min -ad­dJms­Des­tin­a­tion jms/mytopic topic
                                                                                      j2eead­min -ad­dJms­Des­tin­a­tion jms/myqueue queue
                                                                                      

                                                                                      管理(创建)通道后,JMS 客户端代码就可以访问它们。

                                                                                      Once the chan­nels have been ad­min­istered (cre­ated), they can be ac­cessed by JMS client code.

                                                                                      上下文 jndiContext = new InitialContext();
                                                                                      队列 myQueue = (Queue) jndiContext.lookup("jms/myqueue");
                                                                                      主题 myTopic = (主题) jndiContext.lookup("jms/mytopic");
                                                                                      
                                                                                      Con­text jn­diCon­text = new Ini­tial­Con­text();
                                                                                      Queue myQueue = (Queue) jn­diCon­text.lookup("jms/myqueue");
                                                                                      Topic myTopic = (Topic) jn­diCon­text.lookup("jms/mytopic");
                                                                                      

                                                                                      JNDI 查找不会创建队列(或主题);它已经由j2eeadmin命令创建。JNDI 查找只是在 Java 中创建一个Queue实例,该实例对消息传递系统中的队列结构进行建模并提供对队列结构的访问。

                                                                                      The JNDI lookup doesn't create the queue (or topic); it was already cre­ated by the j2eead­min com­mand. The JNDI lookup simply cre­ates a Queue in­stance in Java that models and provides access to the queue struc­ture in the mes­saging system.



                                                                                      示例: IBM WebSphere MQ

                                                                                      Ex­ample: IBM Web­Sphere MQ

                                                                                      如果您的消息传递系统实现是 IBM 的 WebSphere MQ for Java(它实现了 JMS),那么您将使用 WebSphere MQ JMS 管理工具来创建目标。这将创建一个名为myQueue的队列。

                                                                                      If your mes­saging system im­ple­ment­a­tion is IBM's Web­Sphere MQ for Java, which im­ple­ments JMS, you'll use the Web­Sphere MQ JMS ad­min­is­tra­tion tool to create des­tin­a­tions. This will create a queue named myQueue.

                                                                                      定义 Q(myQueue)
                                                                                      
                                                                                      DEFINE Q(myQueue)
                                                                                      

                                                                                      一旦该队列存在于 WebSphere MQ 中,应用程序就可以访问该队列。

                                                                                      Once that queue exists in Web­Sphere MQ, an ap­plic­a­tion can access the queue.

                                                                                      WebSphere MQ 没有完整的 WebSphere Application Server,因此不包含 JNDI 实现,因此我们无法像在 J2EE 示例中那样使用 JNDI 来查找队列。相反,我们必须通过 JMS 会话访问队列,如下所示。

                                                                                      Web­Sphere MQ, without the full Web­Sphere Ap­plic­a­tion Server, does not in­clude a JNDI im­ple­ment­a­tion, so we cannot use JNDI to look up the queue as we did in the J2EE ex­ample. Rather, we must access the queue via a JMS ses­sion, like this.

                                                                                      Session session = // 创建会话
                                                                                      队列队列 = session.createQueue("myQueue");
                                                                                      
                                                                                      Ses­sion ses­sion = // create the ses­sion
                                                                                      Queue queue = ses­sion.cre­ateQueue("myQueue");
                                                                                      



                                                                                      示例: 微软 MSMQ

                                                                                      Ex­ample: Mi­crosoft MSMQ

                                                                                      MSMQ 提供了多种不同的方法来创建消息通道(称为队列)。您可以使用 Microsoft Message Queue Explorer 或计算机管理控制台创建队列(见图)。从这里您可以设置队列属性或删除队列。

                                                                                      MSMQ provides a number of dif­fer­ent ways to create a mes­sage chan­nel, called a queue. You can create a queue using the Mi­crosoft Mes­sage Queue Ex­plorer or the Com­puter Man­age­ment con­sole (see figure). From here you can set queue prop­er­ties or delete queues.

                                                                                      图形/03inf04.gif

                                                                                      或者,您可以使用代码创建队列。

                                                                                      Al­tern­at­ively, you can create the queue using code.

                                                                                      使用系统消息传递;
                                                                                      ...
                                                                                      MessageQueue.Create("MyQ​​ueue");
                                                                                      
                                                                                      using System.Mes­saging;
                                                                                      ...
                                                                                      Mes­sageQueue.Create("MyQueue");
                                                                                      

                                                                                      创建队列后,应用程序可以通过创建MessageQueue实例并传递队列名称来访问它。

                                                                                      Once the queue is cre­ated, an ap­plic­a­tion can access it by cre­at­ing a Mes­sageQueue in­stance, passing the name of the queue.

                                                                                      MessageQueue mq = new MessageQueue("MyQ​​ueue");
                                                                                      
                                                                                      Mes­sageQueue mq = new Mes­sageQueue("MyQueue");
                                                                                      



                                                                                        信息

                                                                                        Message

                                                                                        图形/message_icon.gif

                                                                                        企业有两个独立的应用程序,它们通过消息传递进行通信,并使用连接它们的消息通道。

                                                                                        An en­ter­prise has two sep­ar­ate ap­plic­a­tions that are com­mu­nic­at­ing via Mes­saging, using a Mes­sage Chan­nel that con­nects them.

                                                                                        通过消息通道连接的两个应用程序如何交换一条信息?

                                                                                        How can two ap­plic­a­tions con­nec­ted by a Mes­sage Chan­nel ex­change a piece of in­form­a­tion?



                                                                                        消息通道通常可以被视为管道,即从一个应用程序到另一个应用程序的管道。那么,数据可以像水一样倒入一端,然后从另一端流出,这可能是合情合理的。但大多数应用程序数据并不是一个连续的流;而是一种连续的数据流。它由记录、对象、数据库行等单元组成。因此通道必须传输数据单元。

                                                                                        A Mes­sage Chan­nel can often be thought of as a pipe, a con­duit from one ap­plic­a­tion to an­other. It might stand to reason then that data could be poured into one end, like water, and it would come flow­ing out of the other end. But most ap­plic­a­tion data isn't one con­tinu­ous stream; it con­sists of units, such as re­cords, ob­jects, data­base rows, and the like. So a chan­nel must trans­mit units of data.

                                                                                        “传输”数据是什么意思?在函数调用中,调用者可以通过传递指向内存中数据地址的指针来按引用传递参数;这是有效的,因为调用者和函数共享相同的内存堆。类似地,同一进程中的两个线程可以通过传递指针来传递记录或对象,因为它们共享相同的内存空间。

                                                                                        What does it mean to "trans­mit" data? In a func­tion call, the caller can pass a para­meter by ref­er­ence by passing a pointer to the data's ad­dress in memory; this works be­cause both the caller and the func­tion share the same memory heap. Sim­il­arly, two threads in the same pro­cess can pass a record or object by passing a pointer, since they both share the same memory space.

                                                                                        传递一段数据的两个独立进程还有更多工作要做。由于它们各自有自己的内存空间,因此它们必须将数据从一个内存空间复制到另一个内存空间。数据通常以字节流的形式传输,这是最基本的数据形式。这意味着第一个进程必须将数据编组为字节形式,然后将其从第一个进程复制到第二个进程;第二个进程会将数据解组回其原始形式,以便第二个进程在第一个进程中拥有原始数据的副本。封送处理是远程过程调用 (RPC) 如何将参数发送到远程进程以及进程如何返回结果的方式。

                                                                                        Two sep­ar­ate pro­cesses passing a piece of data have more work to do. Since they each have their own memory space, they have to copy the data from one memory space to the other. The data is usu­ally trans­mit­ted as a byte stream, the most basic form of data. This means that the first pro­cess must mar­shal the data into byte form, and then copy it from the first pro­cess to the second one; the second pro­cess will un­mar­shal the data back into its ori­ginal form, such that the second pro­cess then has a copy of the ori­ginal data in the first pro­cess. Mar­shal­ing is how a Remote Pro­ced­ure Call (RPC) sends ar­gu­ments to the remote pro­cess and how the pro­cess re­turns the result.

                                                                                        因此,消息传递传输离散的数据单元,它通过对来自发送方的数据进行编组并在接收方中对其进行解组来实现这一点,以便接收方拥有自己的本地副本。有用的是一种包装数据单元的简单方法,以便适合在消息传递通道上传输数据。

                                                                                        So mes­saging trans­mits dis­crete units of data, and it does so by mar­shal­ing the data from the sender and un­mar­shal­ing it in the re­ceiver so that the re­ceiver has its own local copy. What would be help­ful would be a simple way to wrap a unit of data such that it is ap­pro­pri­ate to trans­mit the data on a mes­saging chan­nel.

                                                                                        将信息打包成Message ,这是消息传递系统可以通过消息通道传输的数据记录。

                                                                                        Pack­age the in­form­a­tion into a Mes­sage, a data record that the mes­saging system can trans­mit through a Mes­sage Chan­nel.

                                                                                        图形/03inf05.gif



                                                                                        因此,要通过消息传递系统传输的任何数据都必须转换为可以通过消息传递通道发送的一个或多个消息。

                                                                                        Thus, any data that is to be trans­mit­ted via a mes­saging system must be con­ver­ted into one or more mes­sages that can be sent through mes­saging chan­nels.

                                                                                        消息由两个基本部分组成。

                                                                                        A mes­sage con­sists of two basic parts.

                                                                                        1. 报头 消息传递系统使用的信息,描述正在传输的数据、其来源、目的地等。

                                                                                        2. Header In­form­a­tion used by the mes­saging system that de­scribes the data being trans­mit­ted, its origin, its des­tin­a­tion, and so on.

                                                                                        3. 正文 正在传输的数据,通常会被消息传递系统忽略并简单地按原样传输。

                                                                                        4. Body The data being trans­mit­ted, which is gen­er­ally ig­nored by the mes­saging system and simply trans­mit­ted as is.

                                                                                        这个概念并不是消息传递所独有的。邮政服务邮件和电子邮件都以离散邮件消息的形式发送数据。以太网以数据包的形式传输数据,TCP/IP 的 IP 部分(例如互联网)也是如此。互联网上的流媒体实际上是一系列的数据包。

                                                                                        This concept is not unique to mes­saging. Both postal ser­vice mail and e-mail send data as dis­crete mail mes­sages. An Eth­er­net net­work trans­mits data as pack­ets, as does the IP part of TCP/IP such as the In­ter­net. Stream­ing media on the In­ter­net is ac­tu­ally a series of pack­ets.

                                                                                        对于消息传递系统来说,所有消息都是相同的:如标头所描述的要传输的某些数据体。然而,对于应用程序程序员来说,存在不同类型的消息,即不同的应用程序使用风格。使用命令消息调用另一个应用程序中的过程。使用文档消息将一组数据传递到另一个应用程序。使用事件消息通知另一个应用程序此应用程序中的更改。如果其他应用程序应发回回复,请使用Request-Reply

                                                                                        To the mes­saging system, all mes­sages are the same: some body of data to be trans­mit­ted as de­scribed by the header. How­ever, to the ap­plic­a­tions pro­gram­mer, there are dif­fer­ent types of mes­sagesthat is, dif­fer­ent ap­plic­a­tion styles of use. Use a Com­mand Mes­sage to invoke a pro­ced­ure in an­other ap­plic­a­tion. Use a Doc­u­ment Mes­sage to pass a set of data to an­other ap­plic­a­tion. Use an Event Mes­sage to notify an­other ap­plic­a­tion of a change in this ap­plic­a­tion. If the other ap­plic­a­tion should send back a reply, use Re­quest-Reply.

                                                                                        如果应用程序希望发送的信息多于一条消息所能容纳的信息,请将数据分成更小的部分并将这些部分作为消息序列发送。如果数据仅在有限的时间内有用,请将此使用时间指定为Message Expiration 。由于消息的所有不同发送者和接收者必须就消息中的数据格式达成一致,因此将格式指定为规范数据模型

                                                                                        If an ap­plic­a­tion wishes to send more in­form­a­tion than one mes­sage can hold, break the data into smal­ler parts and send the parts as a Mes­sage Se­quence. If the data is only useful for a lim­ited amount of time, spe­cify this use-by time as a Mes­sage Ex­pir­a­tion. Since all the vari­ous senders and re­ceiv­ers of mes­sages must agree on the format of the data in the mes­sages, spe­cify the format as a Ca­non­ical Data Model.

                                                                                        示例: JMS 消息

                                                                                        Ex­ample: JMS Mes­sage

                                                                                        在 JMS 中,消息由Message 类型表示,该类型有多个子类型。每个子类型中,头结构都是相同的;这是因类型而异的正文格式。

                                                                                        In JMS, a mes­sage is rep­res­en­ted by the type Mes­sage, which has sev­eral sub­types. In each sub­type, the header struc­ture is the same; it's the body format that varies by type.

                                                                                        1. TextMessage 最常见的消息类型。正文是一个字符串,例如文字文本或 XML 文档。textMessage.getText()以String形式返回消息正文。

                                                                                        2. Text­Mes­sage The most common type of mes­sage. The body is a string, such as lit­eral text or an XML doc­u­ment. text­Mes­sage.get­Text() re­turns the mes­sage body as a String.

                                                                                        3. BytesMessage 最简单、最通用的消息类型。主体是一个字节数组。bytesMessage.readBytes(byteArray)将内容复制到指定的字节数组中。

                                                                                        4. BytesMes­sage The simplest, most uni­ver­sal type of mes­sage. The body is a byte array. bytesMes­sage.read­Bytes(byte­Array) copies the con­tents into the spe­cified byte array.

                                                                                        5. ObjectMessage 主体是单个 Java 对象,特别是实现java.io.Serializing 的对象,它允许对对象进行封送和取消封送。objectMessage.getObject()返回可序列化的

                                                                                        6. Ob­ject­Mes­sage The body is a single Java object, spe­cific­ally one that im­ple­ments java.io.Seri­al­iz­able, which en­ables the object to be mar­shaled and un­mar­shaled. ob­ject­Mes­sage.getO­b­ject() re­turns the Seri­al­iz­able.

                                                                                        7. StreamMessage主体是 Java 原语的流。接收方使用readBoolean()readChar()readDouble()等方法从消息中读取数据。

                                                                                        8. StreamMes­sage The body is a stream of Java prim­it­ives. The re­ceiver uses meth­ods like read­Boolean(), read­Char(), and re­ad­Double() to read the data from the mes­sage.

                                                                                        9. MapMessage主体的行为类似于java.util.Map ,其中键是String。接收方使用getBoolean("isEnabled")getInt("numberOfItems")等方法从消息中读取数据。

                                                                                        10. MapMes­sage The body acts like a java.util.Map, where the keys are Strings. The re­ceiver uses meth­ods like get­Boolean("is­En­abled") and getInt("num­ber­OfItems") to read the data from the mes­sage.



                                                                                        示例: .NET 消息

                                                                                        Ex­ample: .NET Mes­sage

                                                                                        在.NET 中,Message类实现消息类型。它有一个属性Body ,其中包含作为Object的消息内容;BodyStream 将内容存储为Stream 。另一个属性BodyType指定正文包含的数据类型,例如字符串、日期、货币、数字或任何其他对象。

                                                                                        In .NET, the Mes­sage class im­ple­ments the mes­sage type. It has a prop­erty, Body, which con­tains the con­tents of the mes­sage as an Object; BodyS­tream stores the con­tents as a Stream. An­other prop­erty, Bo­dy­Type, spe­cifies the type of data the body con­tains, such as a string, a date, a cur­rency, a number, or any other object.



                                                                                        示例: SOAP 消息

                                                                                        Ex­ample: SOAP Mes­sage

                                                                                        在 SOAP 协议 [ SOAP 1.1 ] 中,SOAP 消息是Message 的一个示例。SOAP 消息是一个 XML 文档,它是一个信封(根SOAP-ENV:Envelope元素),其中包含可选标头(SOAP -ENV:Header元素)和必需的正文(SOAP -ENV:Body元素)。这个XML文档是一个可以传输的原子数据记录(通常传输协议是HTTP),因此它是一条消息。

                                                                                        In the SOAP pro­tocol [SOAP 1.1], a SOAP mes­sage is an ex­ample of Mes­sage. A SOAP mes­sage is an XML doc­u­ment that is an en­vel­ope (a root SOAP-ENV:En­vel­ope ele­ment) that con­tains an op­tional header (a SOAP-ENV:Header ele­ment) and re­quired body (a SOAP-ENV:Body ele­ment). This XML doc­u­ment is an atomic data record that can be trans­mit­ted (typ­ic­ally the trans­mis­sion pro­tocol is HTTP) so it is a mes­sage.

                                                                                        下面是 SOAP 规范中的 SOAP 消息示例,其中显示了包含标头和正文的信封。

                                                                                        Here is an ex­ample of a SOAP mes­sage from the SOAP spec that shows an en­vel­ope con­tain­ing a header and a body.

                                                                                        
                                                                                        <SOAP-ENV:信封
                                                                                          xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
                                                                                          SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap
                                                                                        图形/ccc.gif/编码/“/>
                                                                                           <SOAP-ENV:标头>
                                                                                               <t:交易
                                                                                                   xmlns:t="某些 URI"
                                                                                                   SOAP-ENV:必须理解=“1”>
                                                                                                       5
                                                                                               </t:交易>
                                                                                           </SOAP-ENV:标头>
                                                                                           <SOAP-ENV:正文>
                                                                                               <m:GetLastTradePrice xmlns:m="Some-URI">
                                                                                                   <符号>DEF</符号>
                                                                                               </m:获取最后交易价格>
                                                                                           </SOAP-ENV:正文>
                                                                                        </SOAP-ENV:信封>
                                                                                        
                                                                                        
                                                                                        <SOAP-ENV:En­vel­ope
                                                                                          xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/en­vel­ope/"
                                                                                          SOAP-ENV:en­cod­ing­Style="http://schemas.xmlsoap.org/soap
                                                                                        /en­cod­ing/"/>
                                                                                           <SOAP-ENV:Header>
                                                                                               <t:Trans­ac­tion
                                                                                                   xmlns:t="some-URI"
                                                                                                   SOAP-ENV:mus­tUn­der­stand="1">
                                                                                                       5
                                                                                               </t:Trans­ac­tion>
                                                                                           </SOAP-ENV:Header>
                                                                                           <SOAP-ENV:Body>
                                                                                               <m:Get­LastTrade­Price xmlns:m="Some-URI">
                                                                                                   <symbol>DEF</symbol>
                                                                                               </m:Get­LastTrade­Price>
                                                                                           </SOAP-ENV:Body>
                                                                                        </SOAP-ENV:En­vel­ope>
                                                                                        

                                                                                        SOAP 还演示了消息的递归性质,因为 SOAP 消息可以通过消息传递系统进行传输,这意味着消息传递系统消息对象(例如,JMS中的 javax.jms.Message类型或 JMS 中的System.Messaging.Message类型的对象) .NET)包含 SOAP 消息(XML SOAP-ENV:Envelope文档)。在这种情况下,传输协议不是 HTTP,而是消息传递系统的内部协议(消息系统可能会使用 HTTP 或其他网络协议来传输数据,但消息传递系统使传输可靠)。有关跨不同消息传递系统传输消息的更多信息,请参阅信封包装器

                                                                                        SOAP also demon­strates the re­curs­ive nature of mes­sages, be­cause a SOAP mes­sage can be trans­mit­ted via a mes­saging system, which means that a mes­saging system mes­sage object (e.g., an object of type javax.jms.Mes­sage in JMS or System.Mes­saging.Mes­sage in .NET) con­tains the SOAP mes­sage (the XML SOAP-ENV:En­vel­ope doc­u­ment). In this scen­ario, the trans­port pro­tocol isn't HTTP but the mes­saging system's in­ternal pro­tocol (which in turn may be using HTTP or some other net­work pro­tocol to trans­mit the data, but the mes­saging system makes the trans­mis­sion re­li­able). For more in­form­a­tion on trans­port­ing a mes­sage across a dif­fer­ent mes­saging system, see En­vel­ope Wrap­per.



                                                                                          管道和过滤器

                                                                                          Pipes and Filters

                                                                                          图形/pipesandfilters_icon.gif

                                                                                          在许多企业集成场景中,单个事件会触发一系列处理步骤,每个处理步骤执行特定的功能。例如,假设一个新订单以消息的形式到达我们的企业。一项要求可能是对消息进行加密,以防止窃听者刺探客户的订单。第二个要求是消息包含数字证书形式的身份验证信息,以确保订单仅由受信任的客户下达。此外,外部各方可能会发送重复的消息(还记得流行购物网站上仅单击“立即订购”按钮一次的所有警告吗?)。为了避免重复发货和顾客不满意,我们需要在启动后续订单处理步骤之前消除重复的消息。为了满足这些要求,我们需要将一系列可能重复的、包含额外身份验证数据的加密消息转换为一系列唯一的、简单的纯文本订单消息,不含无关的数据字段。

                                                                                          In many en­ter­prise in­teg­ra­tion scen­arios, a single event trig­gers a se­quence of pro­cess­ing steps, each per­form­ing a spe­cific func­tion. For ex­ample, let's assume a new order ar­rives in our en­ter­prise in the form of a mes­sage. One re­quire­ment may be that the mes­sage is en­cryp­ted to pre­vent eaves­drop­pers from spying on a cus­tomer's order. A second re­quire­ment is that the mes­sages con­tain au­then­tic­a­tion in­form­a­tion in the form of a di­gital cer­ti­fic­ate to ensure that orders are placed only by trus­ted cus­tom­ers. In ad­di­tion, du­plic­ate mes­sages could be sent from ex­ternal parties (re­mem­ber all the warn­ings on the pop­u­lar shop­ping sites to click the Order Now button only once?). To avoid du­plic­ate ship­ments and un­happy cus­tom­ers, we need to elim­in­ate du­plic­ate mes­sages before sub­se­quent order pro­cess­ing steps are ini­ti­ated. To meet these re­quire­ments, we need to trans­form a series of pos­sibly du­plic­ated, en­cryp­ted mes­sages con­tain­ing extra au­then­tic­a­tion data into a series of unique, simple plain-text order mes­sages without the ex­traneous data fields.

                                                                                          如何对消息进行复杂的处理,同时又保持独立性和灵活性?

                                                                                          How can we per­form com­plex pro­cess­ing on a mes­sage while main­tain­ing in­de­pend­ence and flex­ib­il­ity?



                                                                                          一种可能的解决方案是编写一个全面的“传入消息消息处理模块”来执行所有必要的功能。然而,这种方法不灵活且难以测试。如果我们需要添加或删除步骤怎么办?例如,如果大客户可以在专用网络上下订单并且不需要加密怎么办?

                                                                                          One pos­sible solu­tion would be to write a com­pre­hens­ive "in­com­ing mes­sage mas­sa­ging module" that per­forms all the ne­ces­sary func­tions. How­ever, such an ap­proach would be in­flex­ible and dif­fi­cult to test. What if we need to add a step or remove one? For ex­ample, what if orders can be placed by large cus­tom­ers who are on a private net­work and do not re­quire en­cryp­tion?

                                                                                          在单个组件内实现所有功能也减少了重用的机会。创建更小、定义明确的组件使我们能够在其他流程中重用它们。例如,订单状态消息可以被加密,但不需要进行重复数据删除,因为重复的状态请求通常是无害的。将解密函数分离到一个单独的模块中允许我们将该函数重用于其他消息。

                                                                                          Im­ple­ment­ing all func­tions inside a single com­pon­ent also re­duces op­por­tun­it­ies for reuse. Cre­at­ing smal­ler, well-defined com­pon­ents allows us to reuse them in other pro­cesses. For ex­ample, order status mes­sages may be en­cryp­ted but do not need to be de-duped be­cause du­plic­ate status re­quests are gen­er­ally not harm­ful. Sep­ar­at­ing the de­cryp­tion func­tion into a sep­ar­ate module allows us to reuse this func­tion for other mes­sages.

                                                                                          集成解决方案通常连接一组异构系统。因此,不同的处理步骤可能需要在不同的物理机器上执行,例如当各个处理步骤只能在特定的系统上执行时。例如,出于安全原因,解密传入消息所需的私钥可能仅在指定机器上可用,并且无法从任何其他机器访问。这意味着解密组件必须在该指定机器上执行,而其他步骤可以在其他机器上执行。同样,不同的处理步骤可以使用不同的编程语言或技术来实现,从而防止它们在同一进程内运行,甚至在同一台计算机上运行。

                                                                                          In­teg­ra­tion solu­tions typ­ic­ally con­nect a col­lec­tion of het­ero­gen­eous sys­tems. As a result, dif­fer­ent pro­cess­ing steps may need to ex­ecute on dif­fer­ent phys­ical ma­chines, such as when in­di­vidual pro­cess­ing steps can only ex­ecute on spe­cific sys­tems. For ex­ample, it is pos­sible that the private key re­quired to de­crypt in­com­ing mes­sages is only avail­able on a des­ig­nated ma­chine and cannot be ac­cessed from any other ma­chine for se­cur­ity reas­ons. This means that the de­cryp­tion com­pon­ent has to ex­ecute on this des­ig­nated ma­chine, whereas the other steps may ex­ecute on other ma­chines. Like­wise, dif­fer­ent pro­cess­ing steps may be im­ple­men­ted using dif­fer­ent pro­gram­ming lan­guages or tech­no­lo­gies that pre­vent them from run­ning inside the same pro­cess or even on the same com­puter.

                                                                                          在单独的组件中实现每个功能仍然会引入组件之间的依赖关系。例如,如果解密组件用解密结果调用认证组件,那么如果没有认证函数,我们就无法使用解密函数。如果我们能够将现有组件“组合”为一系列处理步骤,使得每个组件独立于系统中的其他组件,那么我们就可以解决这些依赖性。这意味着组件公开通用外部接口,以便它们可以互换。

                                                                                          Im­ple­ment­ing each func­tion in a sep­ar­ate com­pon­ent can still in­tro­duce de­pend­en­cies between com­pon­ents. For ex­ample, if the de­cryp­tion com­pon­ent calls the au­then­tic­a­tion com­pon­ent with the res­ults of the de­cryp­tion, we cannot use the de­cryp­tion func­tion without the au­then­tic­a­tion func­tion. We could re­solve these de­pend­en­cies if we could "com­pose" ex­ist­ing com­pon­ents into a se­quence of pro­cess­ing steps in such a way that each com­pon­ent is in­de­pend­ent from the other com­pon­ents in the system. This would imply that com­pon­ents expose gen­eric ex­ternal in­ter­faces so that they are in­ter­change­able.

                                                                                          如果我们使用异步消息传递,我们应该利用从一个组件向另一个组件发送消息的异步方面。例如,一个组件可以将消息发送到另一个组件以进行进一步处理,而无需等待结果。使用这种技术,我们可以并行处理多个消息,每个组件内处理一个消息。

                                                                                          If we use asyn­chron­ous mes­saging, we should take ad­vant­age of the asyn­chron­ous as­pects of send­ing mes­sages from one com­pon­ent to an­other. For ex­ample, a com­pon­ent can send a mes­sage to an­other com­pon­ent for fur­ther pro­cess­ing without wait­ing for the res­ults. Using this tech­nique, we could pro­cess mul­tiple mes­sages in par­al­lel, one inside each com­pon­ent.

                                                                                          使用管道和过滤器架构风格将较大的处理任务划分为一系列较小的、独立的处理步骤(过滤器),这些步骤通过通道(管道)连接。

                                                                                          Use the Pipes and Fil­ters ar­chi­tec­tural style to divide a larger pro­cess­ing task into a se­quence of smal­ler, in­de­pend­ent pro­cess­ing steps (fil­ters) that are con­nec­ted by chan­nels (pipes).

                                                                                          图形/03inf06.gif



                                                                                          每个过滤器都公开一个非常简单的接口:它接收入站管道上的消息,处理消息,并将结果发布到出站管道。管道将一个过滤器连接到下一个过滤器,将输出消息从一个过滤器发送到下一个过滤器。由于所有组件都使用相同的外部接口,因此可以通过将组件连接到不同的管道来将它们组合成不同的解决方案。我们可以添加新的过滤器、省略现有的过滤器或将它们重新排列成新的序列,而无需更改过滤器本身。过滤器和管道之间的连接有时称为端口。在基本形式中,每个滤波器组件都有一个输入端口和一个输出端口。

                                                                                          Each filter ex­poses a very simple in­ter­face: It re­ceives mes­sages on the in­bound pipe, pro­cesses the mes­sage, and pub­lishes the res­ults to the out­bound pipe. The pipe con­nects one filter to the next, send­ing output mes­sages from one filter to the next. Be­cause all com­pon­ents use the same ex­ternal in­ter­face, they can be com­posed into dif­fer­ent solu­tions by con­nect­ing the com­pon­ents to dif­fer­ent pipes. We can add new fil­ters, omit ex­ist­ing ones, or re­ar­range them into a new se­quenceall without having to change the fil­ters them­selves. The con­nec­tion between filter and pipe is some­times called a port. In the basic form, each filter com­pon­ent has one input port and one output port.

                                                                                          当应用于我们的示例问题时,管道和过滤器架构会产生由两个管道连接的三个过滤器(见图)。我们需要一根额外的管道将消息发送到解密组件,还需要一根管道将明文订单消息从重复数据删除程序发送到订单管理系统。这总共有四个管道。

                                                                                          When ap­plied to our ex­ample prob­lem, the Pipes and Fil­ters ar­chi­tec­ture res­ults in three fil­ters con­nec­ted by two pipes (see figure). We need one ad­di­tional pipe to send mes­sages to the de­cryp­tion com­pon­ent and one to send the clear-text order mes­sages from the de-duper to the order man­age­ment system. This makes a total of four pipes.

                                                                                          管道和过滤器描述了消息传递系统的基本架构风格:各个处理步骤(过滤器)通过消息传递通道(管道)链接在一起。本节和后续部分中的许多模式(例如路由和转换模式)都基于此管道和过滤器架构风格。这使您可以轻松地将单个模式组合成更大的解决方案。

                                                                                          Pipes and Fil­ters de­scribes a fun­da­mental ar­chi­tec­tural style for mes­saging sys­tems: In­di­vidual pro­cess­ing steps (fil­ters) are chained to­gether through the mes­saging chan­nels (pipes). Many pat­terns in this and the fol­low­ing sec­tions, such as rout­ing and trans­form­a­tion pat­terns, are based on this Pipes and Fil­ters ar­chi­tec­tural style. This lets you easily com­bine in­di­vidual pat­terns into larger solu­tions.

                                                                                          管道和过滤器样式使用抽象管道来解耦组件。管道允许一个组件将消息发送到管道中,以便稍后可以由该组件未知的另一个进程使用该消息。这种管道的明显实现是消息通道。通常,消息通道在过滤器之间提供语言、平台和位置独立性。这使我们能够出于依赖性、维护或性能原因灵活地将处理步骤转移到不同的机器。然而,消息通道如果所有组件实际上都可以驻留在同一台机器上,那么消息传递基础设施提供的功能可能会相当重量级。使用简单的内存队列来实现管道会更加高效。因此,设计组件以便它们与抽象管道接口进行通信是很有用的。然后可以将该接口的实现换出以使用消息通道或替代实现(例如内存队列)。消息传递网关描述了如何设计组件以实现这种灵活性。

                                                                                          The Pipes and Fil­ters style uses ab­stract pipes to de­couple com­pon­ents from each other. The pipe allows one com­pon­ent to send a mes­sage into the pipe so that it can be con­sumed later by an­other pro­cess that is un­known to the com­pon­ent. The ob­vi­ous im­ple­ment­a­tion for such a pipe is a Mes­sage Chan­nel. Typ­ic­ally, a Mes­sage Chan­nel provides lan­guage, plat­form, and loc­a­tion in­de­pend­ence between the fil­ters. This af­fords us the flex­ib­il­ity to move a pro­cess­ing step to a dif­fer­ent ma­chine for de­pend­ency, main­ten­ance, or per­form­ance reas­ons. How­ever, a Mes­sage Chan­nel provided by a mes­saging in­fra­struc­ture can be quite heavy­weight if all com­pon­ents can in fact reside on the same ma­chine. Using a simple in-memory queue to im­ple­ment the pipes would be much more ef­fi­cient. There­fore, it is useful to design the com­pon­ents so that they com­mu­nic­ate with an ab­stract pipe in­ter­face. The im­ple­ment­a­tion of that in­ter­face can then be swapped out to use a Mes­sage Chan­nel or an al­tern­at­ive im­ple­ment­a­tion such as an in-memory queue. The Mes­saging Gate­way de­scribes how to design com­pon­ents for this flex­ib­il­ity.

                                                                                          管道和过滤器架构的潜在缺点之一是所需通道的数量较多。首先,通道可能不是无限的资源,因为通道提供缓冲和其他消耗内存和 CPU 周期的功能。此外,将消息发布到通道会涉及一定量的开销,因为数据必须从应用程序内部格式转换为消息传递基础结构自己的格式。在接收端,这个过程必须相反。如果我们使用长链过滤器,我们将付出灵活性的代价,但由于重复的消息数据转换,性能可能会降低。

                                                                                          One of the po­ten­tial down­sides of a Pipes and Fil­ters ar­chi­tec­ture is the larger number of re­quired chan­nels. First, chan­nels may not be an un­lim­ited re­source, since chan­nels provide buf­fer­ing and other func­tions that con­sume memory and CPU cycles. Also, pub­lish­ing a mes­sage to a chan­nel in­volves a cer­tain amount of over­head be­cause the data has to be trans­lated from the ap­plic­a­tion-in­ternal format into the mes­saging in­fra­struc­ture's own format. At the re­ceiv­ing end, this pro­cess has to be re­versed. If we are using a long chain of fil­ters, we are paying for the gain in flex­ib­il­ity with po­ten­tially lower per­form­ance due to re­peated mes­sage data con­ver­sion.

                                                                                          管道和过滤器的纯粹形式允许每个过滤器只有一个输入端口和一个输出端口。在处理消息传递时,我们可以稍微放宽这个属性。组件可以从多个通道消费消息,也可以将消息输出到多个通道(例如,消息路由器)。同样,多个过滤器组件可以使用单个消息通道的消息。点对点通道确保只有一个过滤器组件使用每条消息。

                                                                                          The pure form of Pipes and Fil­ters allows each filter to have only a single input port and a single output port. When deal­ing with Mes­saging, we can relax this prop­erty some­what. A com­pon­ent may con­sume mes­sages off more than one chan­nel and also output mes­sages to more than one chan­nel (for ex­ample, a Mes­sage Router). Like­wise, mul­tiple filter com­pon­ents can con­sume mes­sages off a single Mes­sage Chan­nel. A Point-to-Point Chan­nel en­sures that only one filter com­pon­ent con­sumes each mes­sage.

                                                                                          使用管道和过滤器还可以提高可测试性,这是一个经常被忽视的好处。我们可以通过将测试消息传递给组件并将结果消息与预期结果进行比较来测试每个单独的处理步骤。单独测试和调试每个核心功能会更有效,因为我们可以针对特定功能定制测试机制。例如,为了测试加密/解密功能,我们可以传入大量包含随机数据的消息。在加密和解密每条消息后,我们将其与原始消息进行比较。另一方面,为了测试身份验证,我们需要提供带有与系统中已知用户匹配的特定身份验证代码的消息。

                                                                                          Using Pipes and Fil­ters also im­proves test­abil­ity, an often over­looked be­ne­fit. We can test each in­di­vidual pro­cess­ing step by passing a Test Mes­sage to the com­pon­ent and com­par­ing the result mes­sage to the ex­pec­ted out­come. It is more ef­fi­cient to test and debug each core func­tion in isol­a­tion be­cause we can tailor the test mech­an­ism to the spe­cific func­tion. For ex­ample, to test the en­cryp­tion/de­cryp­tion func­tion we can pass in a large number of mes­sages con­tain­ing random data. After we en­crypt and de­crypt each mes­sage we com­pare it with the ori­ginal. On the other hand, to test au­then­tic­a­tion, we need to supply mes­sages with spe­cific au­then­tic­a­tion codes that match known users in the system.

                                                                                          流水线加工

                                                                                          Pipeline Pro­cess­ing

                                                                                          使用异步消息通道连接组件允许链中的每个单元在自己的线程或自己的进程中操作。当一个单元完成一条消息的处理后,它可以将该消息发送到输出通道并立即开始处理另一条消息。它不必等待后续组件读取并处理消息。这允许多个消息在通过各个阶段时同时进行处理。例如,在第一条消息被解密之后,它可以被传递到认证组件。与此同时,下一条消息已经可以被解密(见图)。我们将这样的配置称为处理管道因为消息流过过滤器就像液体流过管道一样。与严格的顺序处理相比,处理管道可以显着提高系统吞吐量。

                                                                                          Con­nect­ing com­pon­ents with asyn­chron­ous Mes­sage Chan­nels allows each unit in the chain to op­er­ate in its own thread or its own pro­cess. When a unit has com­pleted pro­cess­ing one mes­sage, it can send the mes­sage to the output chan­nel and im­me­di­ately start pro­cess­ing an­other mes­sage. It does not have to wait for the sub­se­quent com­pon­ents to read and pro­cess the mes­sage. This allows mul­tiple mes­sages to be pro­cessed con­cur­rently as they pass through the in­di­vidual stages. For ex­ample, after the first mes­sage has been de­cryp­ted, it can be passed on to the au­then­tic­a­tion com­pon­ent. At the same time, the next mes­sage can already be de­cryp­ted (see figure). We call such a con­fig­ur­a­tion a pro­cess­ing pipeline be­cause mes­sages flow through the fil­ters like liquid flows through a pipe. When com­pared to strictly se­quen­tial pro­cess­ing, a pro­cess­ing pipeline can sig­ni­fic­antly in­crease system through­put.

                                                                                          使用管道和过滤器进行管道处理

                                                                                          Pipeline Pro­cess­ing with Pipes and Fil­ters

                                                                                          图形/03inf07.gif

                                                                                          并行处理

                                                                                          Par­al­lel Pro­cess­ing

                                                                                          然而,整体系统吞吐量受到链中最慢进程的限制。我们可以部署该流程的多个并行实例以提高吞吐量。在这种情况下,需要一个具有竞争消费者的点对点通道来保证通道上的每条消息恰好由 N 个可用处理器之一使用。这使我们能够加快最耗时的流程并提高整体吞吐量。但我们需要注意,此配置可能会导致消息处理无序。如果消息的顺序很关键,我们只能运行每个组件的一个实例,或者必须使用 Resequencer 。

                                                                                          How­ever, the over­all system through­put is lim­ited by the slow­est pro­cess in the chain. We can deploy mul­tiple par­al­lel in­stances of that pro­cess to im­prove through­put. In this scen­ario, a Point-to-Point Chan­nel with Com­pet­ing Con­sumers is needed to guar­an­tee that each mes­sage on the chan­nel is con­sumed by ex­actly one of N avail­able pro­cessors. This allows us to speed up the most time-in­tens­ive pro­cess and im­prove over­all through­put. We need to be aware, though, that this con­fig­ur­a­tion can cause mes­sages to be pro­cessed out of order. If the se­quence of mes­sages is crit­ical, we can run only one in­stance of each com­pon­ent or we must use a Resequen­cer.

                                                                                          通过并行处理提高吞吐量

                                                                                          In­creas­ing Through­put with Par­al­lel Pro­cess­ing

                                                                                          图形/03inf08.gif

                                                                                          例如,如果我们假设解密消息比验证消息慢得多,则可以使用图中所示的配置,运行解密组件的三个并行实例。如果每个过滤器都是无状态的,那么并行过滤器效果最佳,也就是说,它在处理消息后返回到之前的状态。这意味着我们无法轻松运行多个并行的重复数据删除组件,因为该组件维护其已接收的所有消息的历史记录,因此不是无状态的。

                                                                                          For ex­ample, if we assume that de­crypt­ing a mes­sage is much slower than au­then­tic­at­ing it, we can use the con­fig­ur­a­tion shown in the figure, run­ning three par­al­lel in­stances of the de­cryp­tion com­pon­ent. Par­al­lel­iz­ing fil­ters works best if each filter is state­lessthat is, it re­turns to the pre­vi­ous state after a mes­sage has been pro­cessed. This means that we cannot easily run mul­tiple par­al­lel de-dupe com­pon­ents be­cause the com­pon­ent main­tains a his­tory of all mes­sages that it already re­ceived and is there­fore not state­less.

                                                                                          管道和过滤器的历史

                                                                                          His­tory of Pipes and Fil­ters

                                                                                          管道和过滤器架构绝不是一个新概念。这种架构的简单优雅与灵活性和高吞吐量相结合,使我们很容易理解管道和过滤器架构的流行。简单的语义还允许使用形式化方法来描述架构。

                                                                                          Pipes and Fil­ters ar­chi­tec­tures are by no means a new concept. The simple el­eg­ance of this ar­chi­tec­ture com­bined with the flex­ib­il­ity and high through­put makes it easy to un­der­stand the pop­ular­ity of Pipes and Fil­ters ar­chi­tec­tures. The simple se­mantics also allow formal meth­ods to be used to de­scribe the ar­chi­tec­ture.

                                                                                          [ Kahn ] 在 1974 年将 Kahn 过程网络描述为一组通过无限 FIFO(先进先出)通道连接的并行过程。[ Garlan ] 包含关于不同架构风格的好章节,包括管道和过滤器。[ Monroe ]详细论述了架构风格和设计模式之间的关系。[ PLoPD1 ] 包含 Regine Meunier 的“管道和过滤器架构”,它构成了 [ POSA ]中包含的管道和过滤器模式的基础。管道和过滤器的几乎所有与集成相关的实现遵循 [ POSA ]中提出的“场景 IV” ,使用有源过滤器独立地从队列管道拉取、处理和推送。[ POSA ]描述的模式假设每个元素在从一个过滤器传递到另一个过滤器时经历相同的处理步骤。在集成场景中通常不会出现这种情况。在许多情况下,消息是根据消息内容或外部控制动态路由的。事实上,路由在企业集成中非常常见,以至于它有自己的模式,即消息路由器

                                                                                          [Kahn] de­scribed Kahn Pro­cess Net­works in 1974 as a set of par­al­lel pro­cesses that are con­nec­ted by un­boun­ded FIFO (First-In, First-Out) chan­nels. [Garlan] con­tains a good chapter on dif­fer­ent ar­chi­tec­tural styles, in­clud­ing Pipes and Fil­ters. [Monroe] gives a de­tailed treat­ment of the re­la­tion­ships between ar­chi­tec­tural styles and design pat­terns. [PLoPD1] con­tains Regine Meunier's "The Pipes and Fil­ters Ar­chi­tec­ture," which formed the basis for the Pipes and Fil­ters pat­tern in­cluded in [POSA]. Almost all in­teg­ra­tion-re­lated im­ple­ment­a­tions of Pipes and Fil­ters follow the "Scen­ario IV" presen­ted in [POSA], using active fil­ters that pull, pro­cess, and push in­de­pend­ently from and to queuing pipes. The pat­tern de­scribed by [POSA] as­sumes that each ele­ment un­der­goes the same pro­cess­ing steps as it is passed from filter to filter. This is gen­er­ally not the case in an in­teg­ra­tion scen­ario. In many in­stances, mes­sages are routed dy­nam­ic­ally based on mes­sage con­tent or ex­ternal con­trol. In fact, rout­ing is such a common oc­cur­rence in en­ter­prise in­teg­ra­tion that it war­rants its own pat­terns, the Mes­sage Router.

                                                                                          词汇

                                                                                          Vocabulary

                                                                                          在讨论管道和过滤器架构时,我们需要谨慎对待术语过滤器。我们稍后定义两个附加模式:消息过滤器内容过滤器。虽然这两种都是通用过滤器的特殊情况,但这种模式语言中的许多其他模式也是如此。换句话说,模式不必涉及过滤功能(例如,消除字段或消息)才能成为管道和过滤器意义上的过滤器。我们可以通过重命名管道和过滤器架构风格来避免这种混乱。然而,我们认为管道和过滤器这是一个如此重要且被广泛讨论的概念,如果我们给它一个新名称,就会更加令人困惑。我们试图在这些模式中谨慎地使用“过滤器”这个词,并试图弄清楚我们是在谈论管道和过滤器中的通用过滤器,还是过滤消息的消息过滤器/内容过滤器。如果我们认为可能仍然存在混淆,我们将通用过滤器称为组件,这是一个足够通用(并且经常被滥用)的术语,它不应该给我们带来任何麻烦。

                                                                                          When dis­cuss­ing Pipes and Fil­ters ar­chi­tec­tures, we need to be cau­tious with the term filter. We later define two ad­di­tional pat­terns, the Mes­sage Filter and the Con­tent Filter. While both of these are spe­cial cases of a gen­eric filter, so are many other pat­terns in this pat­tern lan­guage. In other words, a pat­tern does not have to in­volve a fil­ter­ing func­tion (e.g., elim­in­at­ing fields or mes­sages) in order to be a filter in the sense of Pipes and Fil­ters. We could have avoided this con­fu­sion by re­nam­ing the Pipes and Fil­ters ar­chi­tec­tural style. How­ever, we felt that Pipes and Fil­ters is such an im­port­ant and widely dis­cussed concept that it would be even more con­fus­ing if we gave it a new name. We are trying to use the word filter cau­tiously through­out these pat­terns and trying to be clear about whether we are talk­ing about a gen­eric filter as in Pipes and Fil­ters or a Mes­sage Filter /Con­tent Filter that fil­ters mes­sages. If we thought there might still be con­fu­sion, we called the gen­eric filter a com­pon­ent, which is a gen­eric enough (and often abused enough) term that it should not get us into any trouble.



                                                                                          管道和过滤器与通信顺序进程 (CSP) 的概念有一些相似之处。由 Hoare 于 1978 年提出 [ CSP],CSP 提供了一个简单的模型来描述并行处理系统中发生的同步问题。CSP 的基本机制是通过输入输出 (I/O) 同步两个进程。当进程 A 指示它已准备好向进程 B 输出,并且进程 B 声明它已准备好从进程 A 输入时,I/O 就会发生。如果其中一个发生而另一个不为真,则该进程将处于等待状态排队直到其他进程准备好。CSP 与集成解决方案的不同之处在于,它们不是松散耦合的,“管道”也不提供任何排队机制。尽管如此,我们还是可以从学术界对 CSP 的广泛研究中受益。

                                                                                          Pipes and Fil­ters share some sim­il­ar­it­ies with the concept of Com­mu­nic­at­ing Se­quen­tial Pro­cesses (CSPs). In­tro­duced by Hoare in 1978 [CSP], CSPs provide a simple model to de­scribe syn­chron­iz­a­tion prob­lems that occur in par­al­lel pro­cess­ing sys­tems. The basic mech­an­ism un­der­ly­ing CSPs is the syn­chron­iz­a­tion of two pro­cesses via input-output (I/O). I/O occurs when pro­cess A in­dic­ates that it is ready to output to pro­cess B, and pro­cess B states that it is ready to input from pro­cess A. If one of these hap­pens without the other being true, the pro­cess is put on a wait queue until the other pro­cess is ready. CSPs are dif­fer­ent from in­teg­ra­tion solu­tions in that they are not as loosely coupled, nor do the "pipes" provide any queuing mech­an­isms. Nev­er­the­less, we can be­ne­fit from the ex­tens­ive treat­ment of CSPs in the aca­demic world.

                                                                                          示例: C# 和 MSMQ 中的简单过滤器

                                                                                          Ex­ample: Simple Filter in C# and MSMQ

                                                                                          以下代码片段显示了具有一个输入端口和一个输出端口的过滤器的通用基类。基本实现只是打印接收到的消息的正文并将其发送到输出端口。更有趣的过滤器将继承Processor类并重写ProcessMessage 方法以对消息执行其他操作,即转换消息内容或将其路由到不同的输出通道。

                                                                                          The fol­low­ing code snip­pet shows a gen­eric base class for a filter with one input port and one output port. The base im­ple­ment­a­tion simply prints the body of the re­ceived mes­sage and sends it to the output port. A more in­ter­est­ing filter would sub­class the Pro­cessor class and over­ride the Pro­cess­Mes­sage method to per­form ad­di­tional ac­tions on the mes­sagethat is, trans­form the mes­sage con­tent or route it to dif­fer­ent output chan­nels.

                                                                                          您注意到处理器在实例化期间接收对输入和输出通道的引用。因此,该类既不依赖于特定通道,也不依赖于任何其他过滤器。这允许我们实例化多个过滤器并以任意配置将它们链接在一起。

                                                                                          You notice that the Pro­cessor re­ceives ref­er­ences to an input and output chan­nel during in­stan­ti­ation. Thus, the class is tied to neither spe­cific chan­nels nor any other filter. This allows us to in­stan­ti­ate mul­tiple fil­ters and to chain them to­gether in ar­bit­rary con­fig­ur­a­tions.

                                                                                          
                                                                                          使用系统;
                                                                                          使用系统消息传递;
                                                                                          
                                                                                          命名空间 PipesAndFilters
                                                                                          {
                                                                                              公共类处理器
                                                                                              {
                                                                                                  受保护的消息队列输入队列;
                                                                                                  受保护的消息队列输出队列;
                                                                                          
                                                                                                  公共处理器(消息队列输入队列、消息队列
                                                                                          图形/ccc.gif输出队列)
                                                                                                  {
                                                                                                      this.inputQueue = inputQueue;
                                                                                                      this.outputQueue = 输出队列;
                                                                                                  }
                                                                                          
                                                                                                  公共无效进程()
                                                                                                  {
                                                                                                      inputQueue.ReceiveCompleted += 新
                                                                                          图形/ccc.gifReceiveCompletedEventHandler(OnReceiveCompleted);
                                                                                                      inputQueue.BeginReceive();
                                                                                                  }
                                                                                          
                                                                                                  私人无效OnReceiveCompleted(对象源,
                                                                                          图形/ccc.gifReceiveCompletedEventArgs asyncResult)
                                                                                                  {
                                                                                                      消息队列 mq = (消息队列)源;
                                                                                          
                                                                                                      消息输入消息 = mq.EndReceive(asyncResult
                                                                                          图形/ccc.gif.AsyncResult);
                                                                                                      inputMessage.Formatter = 新的 XmlMessageFormatter
                                                                                                                                    (new String[] {"系统
                                                                                          图形/ccc.gif.字符串,mscorlib"});
                                                                                          
                                                                                                      消息输出消息 = ProcessMessage(inputMessage);
                                                                                          
                                                                                                      输出队列.Send(outputMessage);
                                                                                          
                                                                                                      mq.BeginReceive();
                                                                                                  }
                                                                                          
                                                                                                  受保护的虚拟消息ProcessMessage(Message m)
                                                                                                  {
                                                                                                      Console.WriteLine("收到消息:" + m.Body);
                                                                                                      返回(米);
                                                                                                  }
                                                                                              }
                                                                                          }
                                                                                          
                                                                                          
                                                                                          using System;
                                                                                          using System.Mes­saging;
                                                                                          
                                                                                          namespace Pipes­And­Fil­ters
                                                                                          {
                                                                                              public class Pro­cessor
                                                                                              {
                                                                                                  pro­tec­ted Mes­sageQueue in­putQueue;
                                                                                                  pro­tec­ted Mes­sageQueue out­putQueue;
                                                                                          
                                                                                                  public Pro­cessor (Mes­sageQueue in­putQueue, Mes­sageQueue
                                                                                           out­putQueue)
                                                                                                  {
                                                                                                      this.in­putQueue = in­putQueue;
                                                                                                      this.out­putQueue = out­putQueue;
                                                                                                  }
                                                                                          
                                                                                                  public void Pro­cess()
                                                                                                  {
                                                                                                      in­putQueue.Re­ceive­Com­pleted += new
                                                                                           Re­ceive­Com­plete­dE­ventHand­ler(On­Re­ceive­Com­pleted);
                                                                                                      in­putQueue.Be­gin­Re­ceive();
                                                                                                  }
                                                                                          
                                                                                                  private void On­Re­ceive­Com­pleted(Object source,
                                                                                           Re­ceive­Com­plete­dEvent­Args asyn­cRes­ult)
                                                                                                  {
                                                                                                      Mes­sageQueue mq = (Mes­sageQueue)source;
                                                                                          
                                                                                                      Mes­sage in­put­Mes­sage = mq.En­dRe­ceive(asyn­cRes­ult
                                                                                          .Asyn­cRes­ult);
                                                                                                      in­put­Mes­sage.Format­ter =  new Xm­lMes­sage­Format­ter
                                                                                                                                    (new String[] {"System
                                                                                          .String,mscorlib"});
                                                                                          
                                                                                                      Mes­sage out­put­Mes­sage = Pro­cess­Mes­sage(in­put­Mes­sage);
                                                                                          
                                                                                                      out­putQueue.Send(out­put­Mes­sage);
                                                                                          
                                                                                                      mq.Be­gin­Re­ceive();
                                                                                                  }
                                                                                          
                                                                                                  pro­tec­ted vir­tual Mes­sage Pro­cess­Mes­sage(Mes­sage m)
                                                                                                  {
                                                                                                      Con­sole.WriteLine("Re­ceived Mes­sage: " + m.Body);
                                                                                                      return (m);
                                                                                                  }
                                                                                              }
                                                                                          }
                                                                                          

                                                                                          这个实现是一个事件驱动的消费者。Process方法注册传入消息,并指示消息传递系统在每次消息到达时调用 OnReceiveCompleted 方法。此方法从传入事件对象中提取消息数据并调用虚拟方法ProcessMessage

                                                                                          This im­ple­ment­a­tion is an Event-Driven Con­sumer. The Pro­cess method re­gisters for in­com­ing mes­sages and in­structs the mes­saging system to invoke the method On­Re­ceive­Com­pleted every time a mes­sage ar­rives. This method ex­tracts the mes­sage data from the in­com­ing event object and calls the vir­tual method Pro­cess­Mes­sage.

                                                                                          这个简单的过滤器示例不是事务性的。如果在处理消息时(在将其发送到输出通道之前)发生错误,则该消息将丢失。这在生产环境中通常是不可取的。请参阅事务客户端以获取此问题的解决方案。

                                                                                          This simple filter ex­ample is not trans­ac­tional. If an error occurs while pro­cess­ing the mes­sage (before it is sent to the output chan­nel), the mes­sage is lost. This is gen­er­ally not de­sir­able in a pro­duc­tion en­vir­on­ment. See Trans­ac­tional Client for a solu­tion to this prob­lem.



                                                                                            消息路由器

                                                                                            Message Router

                                                                                            图形/messagerouter_icon.gif

                                                                                            管道和过滤器链中的多个处理步骤通过消息通道连接

                                                                                            Mul­tiple pro­cess­ing steps in a Pipes and Fil­ters chain are con­nec­ted by Mes­sage Chan­nels.

                                                                                            如何解耦各个处理步骤,以便消息可以根据一组条件传递到不同的过滤器?

                                                                                            How can you de­couple in­di­vidual pro­cess­ing steps so that mes­sages can be passed to dif­fer­ent fil­ters de­pend­ing on a set of con­di­tions?



                                                                                            管道和过滤器架构风格通过固定管道将过滤器直接相互连接。这是有道理的,因为管道和过滤器模式(例如,[ POSA ] )的许多应用程序都基于大量数据项,每个数据项都经历相同的顺序处理步骤。例如,编译器总是首先执行词法分析,其次执行语法分析,最后执行语义分析。另一方面,基于消息的集成解决方案处理不一定与单个较大数据集关联的单独消息。因此,各个消息更有可能需要一系列不同的处理步骤。

                                                                                            The Pipes and Fil­ters ar­chi­tec­tural style con­nects fil­ters dir­ectly to each other with fixed pipes. This makes sense be­cause many ap­plic­a­tions of the Pipes and Fil­ters pat­tern (e.g., [POSA]) are based on a large set of data items, each of which un­der­goes the same se­quen­tial pro­cess­ing steps. For ex­ample, a com­piler will always ex­ecute the lex­ical ana­lysis first, the syn­tactic ana­lysis second, and the se­mantic ana­lysis last. Mes­sage-based in­teg­ra­tion solu­tions, on the other hand, deal with in­di­vidual mes­sages that are not ne­ces­sar­ily as­so­ci­ated with a single, larger data set. As a result, in­di­vidual mes­sages are more likely to re­quire a dif­fer­ent series of pro­cess­ing steps.

                                                                                            消息通道将消息的发送者和接收者解耦。 这也意味着多个应用程序可以将消息发布到消息通道。因此,消息通道可以包含来自不同源的消息,这些消息可能必须根据消息的类型或其他标准进行不同的处理。您可以为每种消息类型创建一个单独的消息通道(稍后将作为数据类型通道更详细地解释这一概念))并将每个通道连接到该消息类型所需的处理步骤。然而,这将要求消息发起者了解不同处理步骤的选择标准,以便将消息发布到正确的通道。它还可能导致消息通道数量的爆炸。 此外,消息经历哪些步骤的决定可能不仅仅取决于消息的来源。例如,我们可以想象这样一种情况:消息的目的地根据迄今为止通过通道的消息数量而变化。没有任何一个发起者知道这个号码,因此无法将消息发送到正确的通道。

                                                                                            A Mes­sage Chan­nel de­couples the sender and the re­ceiver of a Mes­sage. This also means that mul­tiple ap­plic­a­tions can pub­lish Mes­sages to a Mes­sage Chan­nel. As a result, a Mes­sage Chan­nel can con­tain mes­sages from dif­fer­ent sources that may have to be treated dif­fer­ently based on the type of the mes­sage or other cri­teria. You could create a sep­ar­ate Mes­sage Chan­nel for each mes­sage type (a concept ex­plained in more detail later as a Data­type Chan­nel) and con­nect each chan­nel to the re­quired pro­cess­ing steps for that mes­sage type. How­ever, this would re­quire the mes­sage ori­gin­at­ors to be aware of the se­lec­tion cri­teria for dif­fer­ent pro­cess­ing steps in order to pub­lish the mes­sage to the cor­rect chan­nel. It could also lead to an ex­plo­sion of the number of Mes­sage Chan­nels. Fur­ther­more, the de­cision on which steps the mes­sage un­der­goes may not just depend on the origin of the mes­sage. For ex­ample, we could ima­gine a situ­ation where the des­tin­a­tion of a mes­sage varies de­pend­ing on the number of mes­sages that have passed through the chan­nel so far. No single ori­gin­ator would know this number and would there­fore be unable to send the mes­sage to the cor­rect chan­nel.

                                                                                            消息通道提供了应用程序将消息发布到消息不进一步了解该消息的目的地。因此,消息的路径可以根据订阅消息通道的组件而改变。然而,这种类型的“路由”没有考虑各个消息的属性。一旦组件订阅了消息通道,默认情况下它将消耗来自该通道的所有消息,无论单个消息的特定属性如何。此行为类似于在 UNIX 中使用管道符号来处理文本文件。它允许您将进程组成管道和过滤器链,但在链的生命周期内,所有文本行都会经历相同的步骤。

                                                                                            Mes­sage Chan­nels provide a very basic form of rout­ing cap­ab­il­it­ies. An ap­plic­a­tion pub­lishes a Mes­sage to a Mes­sage Chan­nel and has no fur­ther know­ledge of that Mes­sage's des­tin­a­tion. There­fore, the path of the Mes­sage can change de­pend­ing on which com­pon­ent sub­scribes to the Mes­sage Chan­nel. How­ever, this type of "rout­ing" does not take into ac­count the prop­er­ties of in­di­vidual mes­sages. Once a com­pon­ent sub­scribes to a Mes­sage Chan­nel, it will by de­fault con­sume all mes­sages from that chan­nel re­gard­less of the in­di­vidual mes­sage's spe­cific prop­er­ties. This be­ha­vior is sim­ilar to the use of the pipe symbol in UNIX to pro­cess text files. It allows you to com­pose pro­cesses into a Pipes and Fil­ters chain, but for the life­time of the chain, all lines of text un­dergo the same steps.

                                                                                            我们可以让接收组件本身负责确定是否应该处理到达公共消息通道的消息。但这是有问题的,因为一旦消息被使用并且组件确定它不需要该消息,它就不能只是将消息放回到通道上以供另一个组件检出。一些消息系统允许接收者在不从通道中删除消息的情况下检查消息属性,以便决定是否使用该消息。然而,这不是通用的解决方案,并且还将使用组件与特定类型的消息联系起来,因为消息选择的逻辑现在直接内置在组件中。这将降低该组件重用的可能性,并消除作为管道和过滤器模型的关键优势的可组合性。

                                                                                            We could make the re­ceiv­ing com­pon­ent itself re­spons­ible for de­term­in­ing whether it should pro­cess a mes­sage that ar­rives on a common Mes­sage Chan­nel. This is prob­lem­atic, though, be­cause once the mes­sage is con­sumed and the com­pon­ent de­term­ines that it does not want the mes­sage, it can't just put the mes­sage back on the chan­nel for an­other com­pon­ent to check out. Some mes­saging sys­tems allow re­ceiv­ers to in­spect mes­sage prop­er­ties without re­mov­ing the mes­sage from the chan­nel so that it can decide whether to con­sume the mes­sage. How­ever, this is not a gen­eral solu­tion and also ties the con­sum­ing com­pon­ent to a spe­cific type of mes­sage be­cause the logic for mes­sage se­lec­tion is now built right into the com­pon­ent. This would reduce the po­ten­tial for reuse of that com­pon­ent and elim­in­ate the com­pos­ab­il­ity that is the key strength of the Pipes and Fil­ters model.

                                                                                            许多这些替代方案都假设我们可以修改参与的组件来满足我们的需求。然而,在大多数集成解决方案中,构建块(组件)是大型应用程序,在大多数情况下根本无法修改,例如,因为它们是打包应用程序或遗留应用程序。这使得根据消息传递系统或其他应用程序的需要来调整消息生成或消费应用程序是不经济的,甚至是不可能的。

                                                                                            Many of these al­tern­at­ives assume that we can modify the par­ti­cip­at­ing com­pon­ents to meet our needs. In most in­teg­ra­tion solu­tions, how­ever, the build­ing blocks (com­pon­ents) are large ap­plic­a­tions that in most cases cannot be mod­i­fied at allfor ex­ample, be­cause they are pack­aged ap­plic­a­tions or legacy ap­plic­a­tions. This makes it un­eco­nom­ical or even im­pos­sible to adjust the mes­sage-pro­du­cing or -con­sum­ing ap­plic­a­tions to the needs of the mes­saging system or other ap­plic­a­tions.

                                                                                            管道和过滤器的优点之一是各个组件的可组合性。此属性使我们能够在过滤器链中插入额外的步骤,而无需更改现有组件。这提供了通过在两个过滤器之间插入另一个过滤器来解耦两个过滤器的选项,该过滤器确定下一步要执行的步骤。

                                                                                            One ad­vant­age of Pipes and Fil­ters is the com­pos­ab­il­ity of the in­di­vidual com­pon­ents. This prop­erty en­ables us to insert ad­di­tional steps into the filter chain without having to change ex­ist­ing com­pon­ents. This opens up the option of de­coup­ling two fil­ters by in­sert­ing between them an­other filter that de­term­ines what step to ex­ecute next.

                                                                                            插入一个特殊的过滤器,即消息路由器,它使用一个消息通道中的消息并将其重新发布到不同的消息通道,具体取决于一组条件。

                                                                                            Insert a spe­cial filter, a Mes­sage Router, which con­sumes a Mes­sage from one Mes­sage Chan­nel and re­pub­lishes it to a dif­fer­ent Mes­sage Chan­nel, de­pend­ing on a set of con­di­tions.

                                                                                            图形/03inf09.gif



                                                                                            消息路由器与管道和过滤器的基本概念不同,它连接到多个输出通道(即,它有多个输出端口)。然而,由于管道和过滤器架构,消息路由器周围的组件完全不知道消息路由器的存在。他们只是从一个渠道消费消息并将其发布到另一个渠道。消息路由器的一个定义属性是它不会修改消息内容;它只关心消息的目的地。

                                                                                            The Mes­sage Router dif­fers from the basic notion of Pipes and Fil­ters in that it con­nects to mul­tiple output chan­nels (i.e., it has more than one output port). How­ever, thanks to the Pipes and Fil­ters ar­chi­tec­ture, the com­pon­ents sur­round­ing the Mes­sage Router are com­pletely un­aware of the ex­ist­ence of a Mes­sage Router. They simply con­sume mes­sages off one chan­nel and pub­lish them to an­other. A de­fin­ing prop­erty of the Mes­sage Router is that it does not modify the mes­sage con­tents; it con­cerns itself only with the des­tin­a­tion of the mes­sage.

                                                                                            使用消息路由器的主要好处是消息目的地的决策标准保存在单个位置。如果定义了新的消息类型,添加了新的处理组件,或者路由规则发生了变化,我们只需要更改消息路由器逻辑,而所有其他组件不受影响。此外,由于所有消息都通过单个消息路由器,因此保证传入的消息按照正确的顺序一一处理。

                                                                                            The key be­ne­fit of using a Mes­sage Router is that the de­cision cri­teria for the des­tin­a­tion of a mes­sage are main­tained in a single loc­a­tion. If new mes­sage types are defined, new pro­cess­ing com­pon­ents are added, or rout­ing rules change, we need to change only the Mes­sage Router logic, while all other com­pon­ents remain un­af­fected. Also, since all mes­sages pass through a single Mes­sage Router, in­com­ing mes­sages are guar­an­teed to be pro­cessed one by one in the cor­rect order.

                                                                                            虽然消息路由器的目的是使过滤器彼此解耦,但使用消息路由器实际上可能会导致相反的效果。消息路由器组件必须了解所有可能的目标通道,以便将消息发送到正确的通道。如果可能的目的地列表频繁更改,消息路由器可能会成为维护瓶颈。在这些情况下,最好让各个收件人决定他们对哪些消息感兴趣。您可以通过使用发布-订阅通道消息过滤器数组来实现此目的。我们通过称为预测路由反应式过滤来对比这两种替代方案(有关更详细的比较,请参阅第 7消息章路由”中的消息过滤器)。

                                                                                            While the intent of a Mes­sage Router is to de­couple fil­ters from each other, using a Mes­sage Router can ac­tu­ally cause the op­pos­ite effect. The Mes­sage Router com­pon­ent must have know­ledge of all pos­sible des­tin­a­tion chan­nels in order to send the mes­sage to the cor­rect chan­nel. If the list of pos­sible des­tin­a­tions changes fre­quently, the Mes­sage Router can turn into a main­ten­ance bot­tle­neck. In those cases, it would be better to let the in­di­vidual re­cip­i­ents decide which mes­sages they are in­ter­ested in. You can ac­com­plish this by using a Pub­lish-Sub­scribe Chan­nel and an array of Mes­sage Fil­ters. We con­trast these two al­tern­at­ives by call­ing them pre­dict­ive rout­ing and re­act­ive fil­ter­ing (for a more de­tailed com­par­ison, see the Mes­sage Filter in Chapter 7, "Mes­sage Rout­ing").

                                                                                            由于消息路由器需要插入额外的处理步骤,因此可能会降低性能。许多基于消息的系统必须先对来自一个通道的消息进行解码,然后才能将其放置到另一通道上,如果消息本身没有真正改变,这会导致计算开销。这种开销可能会将消息路由器变成性能瓶颈。通过并行使用多个路由器或添加额外的硬件,可以最大限度地减少这种影响。因此,消息吞吐量(每时间单位处理的消息数量)可能不会受到影响,但延迟(一条消息通过系统的时间)几乎肯定会增加。

                                                                                            Be­cause a Mes­sage Router re­quires the in­ser­tion of an ad­di­tional pro­cess­ing step, it can de­grade per­form­ance. Many mes­sage-based sys­tems have to decode the mes­sage from one chan­nel before it can be placed on an­other chan­nel, which causes com­pu­ta­tional over­head if the mes­sage itself does not really change. This over­head can turn a Mes­sage Router into a per­form­ance bot­tle­neck. By using mul­tiple routers in par­al­lel or adding ad­di­tional hard­ware, this effect can be min­im­ized. As a result, the mes­sage through­put (number of mes­sages pro­cessed per time unit) may not be im­pacted, but the latency (time for one mes­sage to travel through the system) will almost cer­tainly in­crease.

                                                                                            与大多数优秀工具一样,消息路由器也可能被滥用。故意使用消息路由器可能会将松散耦合的优点变成缺点。松散耦合的系统可能会导致难以理解解决方案的“大局”(即通过系统的整体消息流)。这是消息传递解决方案的常见问题,而使用路由器可能会加剧该问题。如果一切都与其他一切松散耦合,那么就不可能理解消息实际流向的方向。这会使测试、调试和维护变得复杂。许多工具可以帮助缓解这个问题。首先,我们可以使用消息历史记录在运行时检查消息并查看它们遍历了哪些组件。或者,我们可以编译系统中每个组件订阅或发布的所有通道的列表。有了这些知识,我们就可以绘制跨组件的所有可能消息流的图表。许多 EAI 包在中央存储库中维护频道订阅信息,使这种类型的静态分析变得更加容易。

                                                                                            Like most good tools, Mes­sage Routers can also be abused. De­lib­er­ate use of Mes­sage Routers can turn the ad­vant­age of loose coup­ling into a dis­ad­vant­age. Loosely coupled sys­tems can make it dif­fi­cult to un­der­stand the "big pic­ture" of the solu­tionthe over­all flow of mes­sages through the system. This is a common prob­lem with mes­saging solu­tions, and the use of routers can ex­acer­bate the prob­lem. If everything is loosely coupled to everything else, it be­comes im­pos­sible to un­der­stand in which dir­ec­tion mes­sages ac­tu­ally flow. This can com­plic­ate test­ing, de­bug­ging, and main­ten­ance. A number of tools can help al­le­vi­ate this prob­lem. First, we can use the Mes­sage His­tory to in­spect mes­sages at runtime and see which com­pon­ents they tra­versed. Al­tern­at­ively, we can com­pile a list of all chan­nels to which each com­pon­ent in the system sub­scribes or pub­lishes. With this know­ledge we can draw a graph of all pos­sible mes­sage flows across com­pon­ents. Many EAI pack­ages main­tain chan­nel sub­scrip­tion in­form­a­tion in a cent­ral re­pos­it­ory, making this type of static ana­lysis easier.

                                                                                            消息路由器变体

                                                                                            Mes­sage Router Vari­ants

                                                                                            消息路由器可以使用任意数量的标准来确定传入消息的输出通道。最简单的情况是固定路由器。在这种情况下,仅定义单个输入通道和单个输出通道。固定路由器从输入通道消耗一条消息并将其发布到输出通道。为什么我们要使用这样一个无脑的路由器?固定路由器可能有助于有意解耦子系统,以便我们稍后可以插入更智能的路由器。或者,我们可能在多个集成解决方案之间中继消息。在大多数情况下,固定路由器将与消息转换器或通道适配器结合使用转换消息内容或通过不同的通道类型发送消息。

                                                                                            A Mes­sage Router can use any number of cri­teria to de­term­ine the output chan­nel for an in­com­ing mes­sage. The most trivial case is a fixed router. In this case, only a single input chan­nel and a single output chan­nel are defined. The fixed router con­sumes one mes­sage off the input chan­nel and pub­lishes it to the output chan­nel. Why would we ever use such a brain­less router? A fixed router may be useful to in­ten­tion­ally de­couple sub­sys­tems so that we can insert a more in­tel­li­gent router later. Or, we may be re­lay­ing mes­sages between mul­tiple in­teg­ra­tion solu­tions. In most cases, a fixed router will be com­bined with a Mes­sage Trans­lator or a Chan­nel Ad­apter to trans­form the mes­sage con­tent or send the mes­sage over a dif­fer­ent chan­nel type.

                                                                                            许多消息路由器仅根据消息本身的属性来决定消息目的地,例如消息类型或特定消息字段的值。我们将这样的路由器称为基于内容的路由器。这种类型的路由器非常常见,因此基于内容的路由器模式对其进行了更详细的描述。

                                                                                            Many Mes­sage Routers decide the mes­sage des­tin­a­tion only on prop­er­ties of the mes­sage it­self­for ex­ample, the mes­sage type or the values of spe­cific mes­sage fields. We call such a router a Con­tent-Based Router. This type of router is so common that the Con­tent-Based Router pat­tern de­scribes it in more detail.

                                                                                            其他消息路由器根据环境条件决定消息的目的地。我们将这些路由器称为基于上下文的路由器。此类路由器通常用于执行负载平衡、测试或故障转移功能。例如,如果处理组件发生故障,基于上下文的路由器可以将消息重新路由到另一个处理组件,从而提供故障转移能力。其他路由器将消息流均匀地分配到多个通道,以实现类似于负载均衡器的并行处理。一些消息通道已经提供了基本的负载平衡功能,而无需使用消息路由器,因为多个竞争消费者每个人都可以尽可能快地从同一通道消费消息。但是,消息路由器可以具有额外的内置智能来路由消息,而不是通道实现的简单循环。

                                                                                            Other Mes­sage Routers decide the mes­sage's des­tin­a­tion based on en­vir­on­ment con­di­tions. We call these routers con­text-based routers. Such routers are com­monly used to per­form load-bal­an­cing, test, or fail­over func­tion­al­ity. For ex­ample, if a pro­cess­ing com­pon­ent fails, the con­text-based router can reroute mes­sages to an­other pro­cess­ing com­pon­ent and thus provide fail­over cap­ab­il­ity. Other routers split the flow of mes­sages evenly across mul­tiple chan­nels to achieve par­al­lel pro­cess­ing sim­ilar to a load bal­an­cer. Some Mes­sage Chan­nels already provide basic load-bal­an­cing cap­ab­il­it­ies without the use of a Mes­sage Router be­cause mul­tiple Com­pet­ing Con­sumers can each con­sume mes­sages off the same chan­nel as fast as they can. How­ever, a Mes­sage Router can have ad­di­tional built-in in­tel­li­gence to route the mes­sages as op­posed to a simple round-robin im­ple­men­ted by the chan­nel.

                                                                                            换句话说,许多消息路由器无状态的,它们一次只查看一条消息来做出路由决策。其他路由器在做出路由决策时会考虑先前消息的内容。例如,管道和过滤器示例使用的路由器通过保留已接收到的所有消息的列表来消除重复消息。这些路由器是有状态的。

                                                                                            Many Mes­sage Routers are state­lessin other words, they look at only one mes­sage at a time to make the rout­ing de­cision. Other routers take the con­tent of pre­vi­ous mes­sages into ac­count when making a rout­ing de­cision. For ex­ample, the Pipes and Fil­ters ex­ample used a router that elim­in­ates du­plic­ate mes­sages by keep­ing a list of all mes­sages it already re­ceived. These routers are state­ful.

                                                                                            大多数消息路由器包含用于路由决策的硬编码逻辑。但是,某些变体连接到控制总线,以便中间件解决方案可以更改决策标准,而无需进行任何代码更改或中断消息流。例如,控制总线可以将全局变量的值传播到系统中的所有消息路由器。这对于允许消息系统从测试模式切换到生产模式的测试非常有用。动态路由器根据来自每个潜在接收者的控制消息动态地配置自身。

                                                                                            Most Mes­sage Routers con­tain hard-coded logic for the rout­ing de­cision. How­ever, some vari­ants con­nect to a Con­trol Bus so that the mid­dle­ware solu­tion can change the de­cision cri­teria without having to make any code changes or in­ter­rupt­ing the flow of mes­sages. For ex­ample, the Con­trol Bus can propag­ate the value of a global vari­able to all Mes­sage Routers in the system. This can be very useful for test­ing to allow the mes­saging system to switch from test to pro­duc­tion mode. The Dy­namic Router con­fig­ures itself dy­nam­ic­ally based on con­trol mes­sages from each po­ten­tial re­cip­i­ent.

                                                                                            第 7 章“消息路由”介绍了消息路由器的更多变体。

                                                                                            Chapter 7, "Mes­sage Rout­ing," in­tro­duces more vari­ants of the Mes­sage Router.

                                                                                            示例: 商业 EAI 工具

                                                                                            Ex­ample: Com­mer­cial EAI Tools

                                                                                            消息路由器的概念是消息代理概念的核心,几乎在所有商业 EAI 工具中都实现了这一概念。这些工具接受传入消息,验证它们,转换它们,并将它们路由到正确的目的地。这种架构使参与的应用程序不必完全了解其他应用程序,因为Message Broker应用程序之间的经纪人。这是企业集成中的一个关键功能,因为大多数要连接的应用程序都是打包的或遗留的应用程序,并且集成必须以非侵入方式进行,即不更改应用程序代码。因此,中间件必须合并所有路由逻辑,因此应用程序不必这样做。Message Broker是 [ GoF ] 中提供的中介器的集成等价物。

                                                                                            The notion of a Mes­sage Router is cent­ral to the concept of a Mes­sage Broker, im­ple­men­ted in vir­tu­ally all com­mer­cial EAI tools. These tools accept in­com­ing mes­sages, val­id­ate them, trans­form them, and route them to the cor­rect des­tin­a­tion. This ar­chi­tec­ture al­le­vi­ates the par­ti­cip­at­ing ap­plic­a­tions from having to be aware of other ap­plic­a­tions al­to­gether be­cause the Mes­sage Broker brokers between the ap­plic­a­tions. This is a key func­tion in en­ter­prise in­teg­ra­tion be­cause most ap­plic­a­tions to be con­nec­ted are pack­aged or legacy ap­plic­a­tions and the in­teg­ra­tion has to happen non­in­trus­iv­elythat is, without chan­ging the ap­plic­a­tion code. There­fore, the mid­dle­ware has to in­cor­por­ate all rout­ing logic so the ap­plic­a­tions do not have to. The Mes­sage Broker is the in­teg­ra­tion equi­val­ent of a Me­di­ator presen­ted in [GoF].



                                                                                            示例: 使用 C# 和 MSMQ 的简单路由器

                                                                                            Ex­ample: Simple Router with C# and MSMQ

                                                                                            此代码示例演示了一个非常简单的路由器,它根据简单的条件将传入消息路由到两个可能的输出通道之一。

                                                                                            This code ex­ample demon­strates a very simple router that routes an in­com­ing mes­sage to one of two pos­sible output chan­nels based on a simple con­di­tion.

                                                                                            
                                                                                            简单路由器类
                                                                                            {
                                                                                                受保护的消息队列inQueue;
                                                                                                受保护的消息队列outQueue1;
                                                                                                受保护的消息队列outQueue2;
                                                                                            
                                                                                                公共 SimpleRouter(消息队列 inQueue, 消息队列
                                                                                            图形/ccc.gif输出队列1、消息队列输出队列2)
                                                                                                {
                                                                                                    this.inQueue = inQueue;
                                                                                                    this.outQueue1 = outQueue1;
                                                                                                    this.outQueue2 = outQueue2;
                                                                                            
                                                                                                    inQueue.ReceiveCompleted += 新
                                                                                            图形/ccc.gifReceiveCompletedEventHandler(OnMessage);
                                                                                                    inQueue.BeginReceive();
                                                                                                }
                                                                                                私有无效OnMessage(对象源,
                                                                                            图形/ccc.gifReceiveCompletedEventArgs asyncResult)
                                                                                                {
                                                                                                    消息队列 mq = (消息队列)源;
                                                                                                    消息消息 = mq.EndReceive(asyncResult.AsyncResult);
                                                                                            
                                                                                                    if (IsConditionFulfilled())
                                                                                                        outQueue1.Send(消息);
                                                                                                    别的
                                                                                                        outQueue2.Send(消息);
                                                                                            
                                                                                                    mq.BeginReceive();
                                                                                                }
                                                                                            
                                                                                                受保护的布尔切换= false;
                                                                                            
                                                                                                protected bool IsConditionFulfilled ()
                                                                                                {
                                                                                                    切换=!切换;
                                                                                                    返回切换;
                                                                                                }
                                                                                            
                                                                                            }
                                                                                            
                                                                                            
                                                                                            class Sim­pleRouter
                                                                                            {
                                                                                                pro­tec­ted Mes­sageQueue in­Queue;
                                                                                                pro­tec­ted Mes­sageQueue out­Queue1;
                                                                                                pro­tec­ted Mes­sageQueue out­Queue2;
                                                                                            
                                                                                                public Sim­pleRouter(Mes­sageQueue in­Queue, Mes­sageQueue
                                                                                             out­Queue1, Mes­sageQueue out­Queue2)
                                                                                                {
                                                                                                    this.in­Queue = in­Queue;
                                                                                                    this.out­Queue1 = out­Queue1;
                                                                                                    this.out­Queue2 = out­Queue2;
                                                                                            
                                                                                                    in­Queue.Re­ceive­Com­pleted += new
                                                                                             Re­ceive­Com­plete­dE­ventHand­ler(On­Mes­sage);
                                                                                                    in­Queue.Be­gin­Re­ceive();
                                                                                                }
                                                                                                private void On­Mes­sage(Object source,
                                                                                             Re­ceive­Com­plete­dEvent­Args asyn­cRes­ult)
                                                                                                {
                                                                                                    Mes­sageQueue mq = (Mes­sageQueue)source;
                                                                                                    Mes­sage mes­sage = mq.En­dRe­ceive(asyn­cRes­ult.Asyn­cRes­ult);
                                                                                            
                                                                                                    if (IsCon­di­tion­Ful­filled())
                                                                                                        out­Queue1.Send(mes­sage);
                                                                                                    else
                                                                                                        out­Queue2.Send(mes­sage);
                                                                                            
                                                                                                    mq.Be­gin­Re­ceive();
                                                                                                }
                                                                                            
                                                                                                pro­tec­ted bool toggle = false;
                                                                                            
                                                                                                pro­tec­ted bool IsCon­di­tion­Ful­filled ()
                                                                                                {
                                                                                                    toggle = !toggle;
                                                                                                    return toggle;
                                                                                                }
                                                                                            
                                                                                            }
                                                                                            

                                                                                            代码相对简单。与Pipes 和 Filters中提供的简单过滤器一样, SimpleRouter类使用 C# 委托实现事件驱动的消息使用者。构造函数将方法OnMessage注册为到达inQueue 的消息的处理程序。这会导致 .NET Framework 为到达inQueue调用OnMessage方法。OnMessage通过调用IsConditionFulfilled 方法确定将消息路由到何处。在这个简单的例子中,IsConditionFulfilled只需在两个通道之间切换,将消息序列均匀地分配到outQueue1outQueue2 之间。为了使代码保持最少,这个简单的路由器不是事务性的,也就是说,如果路由器在消耗来自输入通道的消息之后并将其发布到输出通道之前崩溃,则该消息将会丢失。事务性客户端解释了如何使端点事务化。

                                                                                            The code is re­l­at­ively straight­for­ward. Like the simple filter presen­ted in Pipes and Fil­ters, the Sim­pleRouter class im­ple­ments an Event-Driven Con­sumer of mes­sages using C# del­eg­ates. The con­structor re­gisters the method On­Mes­sage as the hand­ler for mes­sages ar­riv­ing on the in­Queue. This causes the .NET Frame­work to invoke the method On­Mes­sage for every mes­sage that ar­rives on the in­Queue. On­Mes­sage fig­ures out where to route the mes­sage by call­ing the method IsCon­di­tion­Ful­filled. In this trivial ex­ample, IsCon­di­tion­Ful­filled simply toggles between the two chan­nels, di­vid­ing the se­quence of mes­sages evenly between out­Queue1 and out­Queue2. In order to keep the code to a min­imum, this simple router is not trans­ac­tion­althat is, if the router crashes after it con­sumes a mes­sage from the input chan­nel and before it pub­lishes it to the output chan­nel, the mes­sage would be lost. Trans­ac­tional Client ex­plains how to make en­d­points trans­ac­tional.



                                                                                              消息翻译器

                                                                                              Message Translator

                                                                                              图形/messagetranslator_icon.gif

                                                                                              前面的模式展示了如何构造消息以及如何将它们路由到正确的目的地。在许多情况下,企业集成解决方案在现有应用程序(例如遗留系统、打包应用程序、自行开发的自定义应用程序或外部合作伙伴运营的应用程序)之间路由消息。这些应用程序中的每一个通常都是围绕专有数据模型构建的。每个应用程序对客户实体、定义客户的属性以及与客户相关的其他实体的概念可能略有不同。例如,会计系统可能对客户的纳税人 ID 号更感兴趣,而客户关系管理 (CRM) 系统则存储电话号码和地址。应用程序的底层数据模型通常驱动物理数据库模式、接口文件格式或应用程序编程接口 (API) 的设计,这些实体是集成解决方案必须与之交互的实体。因此,每个应用程序通常期望接收模仿应用程序内部数据格式的消息。

                                                                                              The pre­vi­ous pat­terns show how to con­struct mes­sages and how to route them to the cor­rect des­tin­a­tion. In many cases, en­ter­prise in­teg­ra­tion solu­tions route mes­sages between ex­ist­ing ap­plic­a­tions such as legacy sys­tems, pack­aged ap­plic­a­tions, homegrown custom ap­plic­a­tions, or ap­plic­a­tions op­er­ated by ex­ternal part­ners. Each of these ap­plic­a­tions is usu­ally built around a pro­pri­et­ary data model. Each ap­plic­a­tion may have a slightly dif­fer­ent notion of the Cus­tomer entity, the at­trib­utes that define a Cus­tomer, and other en­tit­ies to which a Cus­tomer is re­lated. For ex­ample, the ac­count­ing system may be more in­ter­ested in the cus­tomer's tax­payer ID num­bers, whereas the cus­tomer-re­la­tion­ship man­age­ment (CRM) system stores phone num­bers and ad­dresses. The ap­plic­a­tion's un­der­ly­ing data model usu­ally drives the design of the phys­ical data­base schema, an in­ter­face file format, or an ap­plic­a­tion pro­gram­ming in­ter­face (API)those en­tit­ies with which an in­teg­ra­tion solu­tion must in­ter­face. As a result, each ap­plic­a­tion typ­ic­ally ex­pects to re­ceive mes­sages that mimic the ap­plic­a­tion's in­ternal data format.

                                                                                              除了各种应用程序中包含的专有数据模型和数据格式之外,集成解决方案通常还通过独立于特定应用程序的标准化数据格式与外部业务合作伙伴进行交互。许多联盟和标准机构定义了这些协议;例如,RosettaNet、ebXML、OAGIS 和许多其他特定行业联盟。在许多情况下,集成解决方案需要能够使用“官方”数据格式与外部各方进行通信,即使内部系统基于专有格式。

                                                                                              In ad­di­tion to the pro­pri­et­ary data models and data formats in­cor­por­ated in the vari­ous ap­plic­a­tions, in­teg­ra­tion solu­tions often in­ter­act with ex­ternal busi­ness part­ners via stand­ard­ized data formats that are in­de­pend­ent from spe­cific ap­plic­a­tions. A number of con­sor­tia and stand­ards bodies define these pro­to­cols; for ex­ample, Roset­taNet, ebXML, OAGIS, and many other in­dustry-spe­cific con­sor­tia. In many cases, the in­teg­ra­tion solu­tion needs to be able to com­mu­nic­ate with ex­ternal parties using the "of­fi­cial" data formats, even though the in­ternal sys­tems are based on pro­pri­et­ary formats.

                                                                                              使用不同数据格式的系统如何通过消息传递相互通信?

                                                                                              How can sys­tems using dif­fer­ent data formats com­mu­nic­ate with each other using mes­saging?



                                                                                              如果我们可以修改所有应用程序以使用通用数据格式,我们就可以避免转换消息。由于多种原因,这变得很困难(请参阅共享数据库)。首先,更改应用程序的数据格式是有风险的、困难的,并且需要对固有业务功能进行大量更改。对于大多数遗留应用程序来说,更改数据格式在经济上根本不可行。我们可能都记得与 Y2K 改造相关的工作,其中更改的范围仅限于单个字段的大小!

                                                                                              We could avoid having to trans­form mes­sages if we could modify all ap­plic­a­tions to use a common data format. This turns out to be dif­fi­cult for a number of reas­ons (see Shared Data­base). First, chan­ging an ap­plic­a­tion's data format is risky, dif­fi­cult, and re­quires a lot of changes to in­her­ent busi­ness func­tion­al­ity. For most legacy ap­plic­a­tions, data format changes are simply not eco­nom­ic­ally feas­ible. We may all re­mem­ber the effort re­lated to the Y2K ret­ro­fits, where the scope of the change was lim­ited to the size of a single field!

                                                                                              此外,虽然我们可能会让多个应用程序使用相同的数据字段名称甚至相同的数据类型,但物理表示可能仍然有很大不同。一个应用程序可能使用 XML 文档,而另一个应用程序则使用 COBOL copybook。

                                                                                              Also, while we may get mul­tiple ap­plic­a­tions to use the same data field names and maybe even the same data types, the phys­ical rep­res­ent­a­tion may still be quite dif­fer­ent. One ap­plic­a­tion may use XML doc­u­ments, whereas the other ap­plic­a­tion uses COBOL copy­books.

                                                                                              此外,如果我们调整一个应用程序的数据格式以匹配另一应用程序的数据格式,我们就会将这两个应用程序彼此更紧密地联系在一起。企业集成的关键架构原则之一是应用程序之间的松耦合(请参阅规范数据模型)。修改一个应用程序以匹配另一个应用程序的数据格式将违反这一原则,因为它使两个应用程序直接依赖于彼此的内部表示。这消除了替换或更改一个应用程序而不影响另一个应用程序的可能性,这种情况在企业集成中相当常见。

                                                                                              Fur­ther­more, if we adjust the data format of one ap­plic­a­tion to match that of an­other ap­plic­a­tion, we are tying the two ap­plic­a­tions more tightly to each other. One of the key ar­chi­tec­tural prin­ciples in en­ter­prise in­teg­ra­tion is loose coup­ling between ap­plic­a­tions (see Ca­non­ical Data Model). Modi­fy­ing one ap­plic­a­tion to match an­other ap­plic­a­tion's data format would vi­ol­ate this prin­ciple be­cause it makes two ap­plic­a­tions dir­ectly de­pend­ent on each other's in­ternal rep­res­ent­a­tion. This elim­in­ates the pos­sib­il­ity of re­pla­cing or chan­ging one ap­plic­a­tion without af­fect­ing the other ap­plic­a­tion, a scen­ario that is fairly common in en­ter­prise in­teg­ra­tion.

                                                                                              我们可以将数据格式转换直接合并到消息端点中。这样,所有应用程序都将以通用格式而不是应用程序的内部数据格式发布和使用消息。然而,这种方法需要访问端点代码,而打包应用程序通常不是这种情况。此外,将格式转换硬编码到端点将减少代码重用的机会。

                                                                                              We could in­cor­por­ate the data format trans­la­tion dir­ectly into the Mes­sage En­d­point. This way, all ap­plic­a­tions would pub­lish and con­sume mes­sages in a common format as op­posed to in the ap­plic­a­tion's in­ternal data format. How­ever, this ap­proach re­quires access to the en­d­point code, which is usu­ally not the case for pack­aged ap­plic­a­tions. In ad­di­tion, hard-coding the format trans­la­tion to the en­d­point would reduce the op­por­tun­it­ies for code reuse.

                                                                                              在其他过滤器或应用程序之间使用特殊的过滤器(消息转换器)将一种数据格式转换为另一种数据格式。

                                                                                              Use a spe­cial filter, a Mes­sage Trans­lator, between other fil­ters or ap­plic­a­tions to trans­late one data format into an­other.

                                                                                              图形/03inf10.gif



                                                                                              消息转换器是[GoF] 中描述的适配器模式的消息传递等效项。 适配器将组件的接口转换为另一个接口,以便它可以在不同的上下文中使用。

                                                                                              The Mes­sage Trans­lator is the mes­saging equi­val­ent of the Ad­apter pat­tern de­scribed in [GoF]. An ad­apter con­verts the in­ter­face of a com­pon­ent into an­other in­ter­face so it can be used in a dif­fer­ent con­text.

                                                                                              转型水平

                                                                                              Levels of Trans­form­a­tion

                                                                                              消息翻译可能需要在许多不同的级别上进行。例如,数据元素可以共享相同的名称和数据类型,但可以以不同的表示形式使用(例如,XML 文件与逗号分隔值与固定长度字段)。或者,所有数据元素都可以以 XML 格式表示,但使用不同的标签名称。为了总结不同类型的翻译,我们可以将其分为多个层(大致借用 OSI 参考模型)。

                                                                                              Mes­sage trans­la­tion may need to occur at a number of dif­fer­ent levels. For ex­ample, data ele­ments may share the same name and data types but may be used in dif­fer­ent rep­res­ent­a­tions (e.g., XML file vs. comma-sep­ar­ated values vs. fixed-length fields). Or, all data ele­ments may be rep­res­en­ted in XML format but use dif­fer­ent tag names. To sum­mar­ize the dif­fer­ent kinds of trans­la­tion, we can divide it into mul­tiple layers (loosely bor­row­ing from the OSI Ref­er­ence Model).

                                                                                              Layer

                                                                                              处理

                                                                                              Deals With

                                                                                              转型需求(示例)

                                                                                              Trans­form­a­tion Needs (Ex­ample)

                                                                                              工具/技术

                                                                                              Tools/Tech­niques

                                                                                              数据结构(应用层)

                                                                                              Data Struc­tures (Ap­plic­a­tion Layer)

                                                                                              实体、关联、基数

                                                                                              En­tit­ies, as­so­ci­ations, car­din­al­ity

                                                                                              将多对多关系压缩为聚合。

                                                                                              Con­dense many-to-many re­la­tion­ship into ag­greg­a­tion.

                                                                                              结构映射模式、自定义代码

                                                                                              Struc­tural map­ping pat­terns, custom code

                                                                                              数据类型

                                                                                              Data Types

                                                                                              字段名称、数据类型、值域、约束、代码值

                                                                                              Field names, data types, value do­mains, con­straints, code values

                                                                                              将邮政编码从数字转换为字符串。将名字和姓氏字段连接到单个名称字段。将美国州名替换为两个字符的代码。

                                                                                              Con­vert ZIP code from nu­meric to string. Con­cat­en­ate First Name and Last Name fields to single Name field. Re­place U.S. state name with two-char­ac­ter code.

                                                                                              EAI 可视化转换编辑器、XSL、数据库查找、自定义代码

                                                                                              EAI visual trans­form­a­tion ed­it­ors, XSL, data­base look­ups, custom code

                                                                                              数据表示

                                                                                              Data Rep­res­ent­a­tion

                                                                                              数据格式(XML、名称-值对、固定长度数据字段、EAI 供应商格式等)

                                                                                              Data formats (XML, name-value pairs, fixed-length data fields, EAI vendor formats, etc.)

                                                                                              字符集(ASCII、UniCode、EBCDIC)

                                                                                              Char­ac­ter sets (ASCII, Uni­Code, EBCDIC)

                                                                                              加密/压缩

                                                                                              En­cryp­tion/com­pres­sion

                                                                                              解析数据表示并以不同的格式呈现。

                                                                                              Parse data rep­res­ent­a­tion and render in a dif­fer­ent format.

                                                                                              根据需要解密/加密。

                                                                                              De­crypt/en­crypt as ne­ces­sary.

                                                                                              XML 解析器、EAI 解析器/渲染器工具、自定义 API

                                                                                              XML pars­ers, EAI parser/ren­derer tools, custom APIs

                                                                                              运输

                                                                                              Trans­port

                                                                                              通信协议:TCP/IP 套接字、HTTP、SOAP、JMS、TIBCO RendezVous

                                                                                              Com­mu­nic­a­tions pro­to­cols: TCP/IP sock­ets, HTTP, SOAP, JMS, TIBCO Ren­dez­Vous

                                                                                              跨协议移动数据而不影响消息内容。

                                                                                              Move data across pro­to­cols without af­fect­ing mes­sage con­tent.

                                                                                              通道适配器, EAI适配器

                                                                                              Chan­nel Ad­apter, EAI ad­apters

                                                                                              “堆栈”底部的传输层提供不同系统之间的数据传输。它负责跨不同网段的完整可靠的数据传输,并处理丢失的数据包和其他网络错误。一些EAI 供应商提供自己的传输协议(例如TIBCO RendezVous),而其他集成技术则利用TCP/IP 协议(​​例如SOAP)。不同传输层之间的转换可以由通道适配器模式提供。

                                                                                              The Trans­port layer at the bottom of the "stack" provides data trans­fer between the dif­fer­ent sys­tems. It is re­spons­ible for com­plete and re­li­able data trans­fer across dif­fer­ent net­work seg­ments and deals with lost data pack­ets and other net­work errors. Some EAI vendors provide their own trans­port pro­to­cols (e.g., TIBCO Ren­dez­Vous), whereas other in­teg­ra­tion tech­no­lo­gies lever­age TCP/IP pro­to­cols (e.g., SOAP). Trans­la­tion between dif­fer­ent trans­port layers can be provided by the Chan­nel Ad­apter pat­tern.

                                                                                              数据表示层也称为语法。该层定义了传输的数据的表示。这种转换是必要的,因为传输层通常只传输字符或字节流。这意味着复杂的数据结构必须转换为字符串。此转换的常见格式包括 XML、固定长度字段(例如 EDI 记录)和专有格式。在许多情况下,数据也被压缩或加密并带有校验位或数字证书。为了与具有不同数据表示形式的系统进行接口,可能必须对数据进行解密、解压缩和解析,然后必须呈现新的数据格式,并且还可能进行压缩和加密。

                                                                                              The Data Rep­res­ent­a­tion layer is also re­ferred to as the syntax layer. This layer defines the rep­res­ent­a­tion of data that is trans­por­ted. This trans­la­tion is ne­ces­sary be­cause the trans­port layer typ­ic­ally trans­ports only char­ac­ter or byte streams. This means that com­plex data struc­tures have to be con­ver­ted into a char­ac­ter string. Common formats for this con­ver­sion in­clude XML, fixed-length fields (e.g., EDI re­cords), and pro­pri­et­ary formats. In many cases, data is also com­pressed or en­cryp­ted and car­ries check digits or di­gital cer­ti­fic­ates. In order to in­ter­face sys­tems with dif­fer­ent data rep­res­ent­a­tions, data may have to be de­cryp­ted, un­com­pressed, and parsed, and then the new data format must be rendered and pos­sibly com­pressed and en­cryp­ted as well.

                                                                                              数据类型层定义应用程序(域)模型所基于的应用程序数据类型。在这里,我们处理诸如日期字段是否表示为字符串或本机日期结构、日期是否带有时间组件、它们基于哪个时区等决策。我们还可以考虑邮政编码字段是否仅表示美国邮政编码或可以包含加拿大邮政编码。如果是美国邮政编码,是否包含 ZIP+4?是强制性的吗?它存储在一个字段中还是两个字段中?其中许多问题通常在所谓的数据字典中得到解决。与数据类型相关的问题超出了字段是字符串类型还是整数类型的范围。考虑按地区组织的销售数据。一个部门使用的应用程序可以将国家分为四个区域:西部、中部、南部和东部,用字母 W、C、S 和 E 标识。另一个部门可以区分太平洋地区和山区,并区分太平洋地区和山区。从东南向东北。每个区域都由一个两位数的数字来标识。字母E对应什么数字?

                                                                                              The Data Types layer defines the ap­plic­a­tion data types on which the ap­plic­a­tion (domain) model is based. Here we deal with such de­cisions as whether date fields are rep­res­en­ted as strings or as native date struc­tures, whether dates carry a time-of-day com­pon­ent, which time zone they are based on, and so on. We may also con­sider whether the field Postal Code de­notes only a U.S. ZIP code or can con­tain Ca­na­dian postal codes. In the case of a U.S. zip code, do we in­clude a ZIP+4? Is it man­dat­ory? Is it stored in one field, or two? Many of these ques­tions are usu­ally ad­dressed in so-called Data Dic­tion­ar­ies. The issues re­lated to data types go beyond whether a field is of type string or in­teger. Con­sider sales data that is or­gan­ized by region. The ap­plic­a­tion used by one de­part­ment may divide the coun­try into four re­gions: West, Cent­ral, South, and East, iden­ti­fied by the let­ters W, C, S, and E. An­other de­part­ment may dif­fer­en­ti­ate the Pa­cific region from the moun­tain region and dis­tin­guish the North­east from the South­east. Each region is iden­ti­fied by a two-digit number. What number does the letter E cor­res­pond to?

                                                                                              数据结构层描述应用程序域模型级别的数据。因此也称为应用层。该层定义应用程序处理的逻辑实体,例如客户、地址帐户。它还定义了这些实体之间的关系:一个客户可以拥有多个帐户吗?一个客户可以有多个地址吗?客户可以分享地址吗?多个客户可以共用一个账户吗?地址是帐户还是客户的一部分?这是实体关系图和类图的领域。

                                                                                              The Data Struc­tures layer de­scribes the data at the level of the ap­plic­a­tion domain model. It is there­fore also re­ferred to as the ap­plic­a­tion layer. This layer defines the lo­gical en­tit­ies that the ap­plic­a­tion deals with, such as cus­tomer, ad­dress, or ac­count. It also defines the re­la­tion­ships between these en­tit­ies: Can one cus­tomer have mul­tiple ac­counts? Can a cus­tomer have mul­tiple ad­dresses? Can cus­tom­ers share an ad­dress? Can mul­tiple cus­tom­ers share an ac­count? Is the ad­dress part of the ac­count or the cus­tomer? This is the domain of entity-re­la­tion­ship dia­grams and class dia­grams.

                                                                                              解耦的程度

                                                                                              Levels of De­coup­ling

                                                                                              集成中的许多设计权衡都是由解耦组件或应用程序的需要驱动的。解耦是实现变革管理的重要工具。集成通常连接现有应用程序,并且必须适应这些应用程序的更改。消息通道使应用程序不必知道彼此的位置。消息路由器甚至可以使应用程序不必就公共消息通道达成一致。然而,如果应用程序仍然依赖于彼此的数据格式,这种形式的解耦只能在应用程序之间实现有限的独立性。消息翻译器可以消除这种额外的依赖性。

                                                                                              Many of the design trade-offs in in­teg­ra­tion are driven by the need to de­couple com­pon­ents or ap­plic­a­tions. De­coup­ling is an es­sen­tial tool to enable the man­age­ment of change. In­teg­ra­tion typ­ic­ally con­nects ex­ist­ing ap­plic­a­tions and has to ac­com­mod­ate changes to these ap­plic­a­tions. Mes­sage Chan­nels de­couple ap­plic­a­tions from having to know each other's loc­a­tion. A Mes­sage Router can even de­couple ap­plic­a­tions from having to agree on a common Mes­sage Chan­nel. How­ever, this form of de­coup­ling achieves only lim­ited in­de­pend­ence between ap­plic­a­tions if they still depend on each other's data formats. A Mes­sage Trans­lator can remove this ad­di­tional level of de­pend­ency.

                                                                                              链接转换

                                                                                              Chain­ing Trans­form­a­tions

                                                                                              许多业务场景需要不止一层的转换。例如,假设表示为固定格式文件的 EDI 850 采购订单记录必须转换为通过 HTTP 发送到订单管理系统的 XML 文档,该系统使用不同的 Order 对象定义。所需的转换跨越所有四个级别:传输从文件传输更改为HTTP,数据格式从固定字段格式更改为XML,并且数据类型和数据格式都必须转换以符合Order由订单管理系统定义的对象。分层模型的优点在于,您可以处理一层而不必担心较低层,因此可以一次专注于一个抽象级别(请参见下图)。

                                                                                              Many busi­ness scen­arios re­quire trans­form­a­tions at more than one layer. For ex­ample, let's assume an EDI 850 Pur­chase Order record rep­res­en­ted as a fixed-format file has to be trans­lated to an XML doc­u­ment sent over HTTP to the order man­age­ment system, which uses a dif­fer­ent defin­i­tion of the Order object. The re­quired trans­form­a­tion spans all four levels: The trans­port changes from file trans­fer to HTTP, the data format changes from a fixed-field format to XML, and both data types and data formats have to be con­ver­ted to comply with the Order object defined by the order man­age­ment system. The beauty of a layered model is that you can treat one layer without wor­ry­ing about the lower layers and there­fore can focus on one level of ab­strac­tion at a time (see the fol­low­ing figure).

                                                                                              跨多个层的映射

                                                                                              Map­ping Across Mul­tiple Layers

                                                                                              图形/03inf11.gif

                                                                                              使用管道和过滤器链接多个消息转换器单元会产生以下架构(请参见下一页的图)。为每一层创建一个消息转换器允许我们在其他场景中重用这些组件。例如,通道适配器和EDI 到 XML消息转换器可以以通用方式实现,以便它们可以重新用于任何传入的 EDI 文档。

                                                                                              Chain­ing mul­tiple Mes­sage Trans­lator units using Pipes and Fil­ters res­ults in the fol­low­ing ar­chi­tec­ture (see figure on the next page). Cre­at­ing one Mes­sage Trans­lator for each layer allows us to reuse these com­pon­ents in other scen­arios. For ex­ample, the Chan­nel Ad­apter and the EDI-to-XML Mes­sage Trans­lator can be im­ple­men­ted in a gen­eric fash­ion so that they can be reused for any in­com­ing EDI doc­u­ment.

                                                                                              链接多个 消息转换器

                                                                                              Chain­ing Mul­tiple Mes­sage Trans­lat­ors

                                                                                              图形/03inf12.gif

                                                                                              链接多个消息转换器还允许您更改单个层使用的转换,而不影响任何其他层。您可以使用相同的结构转换机制,但不是将数据表示形式转换为固定格式,而是可以通过交换数据表示形式转换将其转换为逗号分隔的文件。

                                                                                              Chain­ing mul­tiple Mes­sage Trans­lators also allows you to change the trans­form­a­tions used at an in­di­vidual layer without af­fect­ing any of the other layers. You could use the same struc­tural trans­form­a­tion mech­an­isms, but in­stead of con­vert­ing the data rep­res­ent­a­tion into a fixed format, you could con­vert it into a comma-sep­ar­ated file by swap­ping out the data rep­res­ent­a­tion trans­form­a­tion.

                                                                                              消息翻译器模式有许多专业化和变体。信封包装器将消息数据包装在信封内,以便可以在消息传递系统中传输。内容丰富器增强消息内的信息,而内容过滤器则删除信息。索赔检查删除信息但将其存储以供以后检索。Normalizer可以将多种不同的消息格式转换为一致的格式。最后,规范数据模型展示了如何利用多个消息转换器s 实现数据格式解耦。在每个模式中,都可能发生复杂的结构转换(例如,将多对多关系映射为一对一关系)。

                                                                                              There are many spe­cial­iz­a­tions and vari­ations of the Mes­sage Trans­lator pat­tern. An En­vel­ope Wrap­per wraps the mes­sage data inside an en­vel­ope so that it can be trans­por­ted across a mes­saging system. A Con­tent En­richer aug­ments the in­form­a­tion inside a mes­sage, whereas the Con­tent Filter re­moves in­form­a­tion. The Claim Check re­moves in­form­a­tion but stores it for later re­trieval. The Nor­mal­izer can con­vert a number of dif­fer­ent mes­sage formats into a con­sist­ent format. Last, the Ca­non­ical Data Model shows how to lever­age mul­tiple Mes­sage Trans­lators to achieve data format de­coup­ling. Inside each of those pat­terns, com­plex struc­tural trans­form­a­tions can occur (e.g., map­ping a many-to-many re­la­tion­ship into a one-to-one re­la­tion­ship).

                                                                                              示例: 使用 XSL 进行结构转换

                                                                                              Ex­ample: Struc­tural Trans­form­a­tion with XSL

                                                                                              转换是一种常见的需求,因此 W3C 定义了一种用于 XML 文档转换的标准语言:可扩展样式表语言 (XSL)。XSL 的一部分是 XSL 转换 (XSLT) 语言,这是一种基于规则的语言,可将一个 XML 文档转换为另一种格式。由于这是一本关于集成而不是关于 XSLT 的书,因此我们仅提供一个简单的示例(有关所有详细细节,请参阅规范 [ XSLT 1.0 ],或者通过查看代码示例来学习,请参阅 [ Tennison ] ) 。为了简单起见,我们通过显示示例 XML 文档而不是 XML 模式来解释所需的转换。

                                                                                              Trans­form­a­tion is such a common need that the W3C defined a stand­ard lan­guage for the trans­form­a­tion of XML doc­u­ments: the Ex­tens­ible Stylesheet Lan­guage (XSL). Part of XSL is the XSL Trans­form­a­tion (XSLT) lan­guage, a rules-based lan­guage that trans­lates one XML doc­u­ment into a dif­fer­ent format. Since this is a book on in­teg­ra­tion and not on XSLT, we just present a simple ex­ample (for all the gory de­tails, see the spec [XSLT 1.0], or to learn by re­view­ing code ex­amples, see [Ten­nison]). In order to keep things simple, we ex­plain the re­quired trans­form­a­tion by show­ing ex­ample XML doc­u­ments as op­posed to XML schemas.

                                                                                              例如,假设我们有一个传入的 XML 文档,需要将其传递到会计系统。如果两个系统都使用 XML,则数据表示层是相同的,我们需要涵盖字段名称、数据类型和结构方面的任何差异。我们假设传入的文档如下所示。

                                                                                              For ex­ample, let's assume we have an in­com­ing XML doc­u­ment and need to pass it to the ac­count­ing system. If both sys­tems use XML, the Data Rep­res­ent­a­tion layer is identical, and we need to cover any dif­fer­ences in field names, data types, and struc­ture. Let's assume the in­com­ing doc­u­ment looks like this.

                                                                                              <数据>
                                                                                                  <顾客>
                                                                                                      <名字>乔</名字>
                                                                                                      <姓氏>母鹿</姓氏>
                                                                                                      <地址类型=“主要”>
                                                                                                          <参考id =“55355”/>
                                                                                                      </地址>
                                                                                                      <地址类型=“次要”>
                                                                                                          <参考id =“77889”/>
                                                                                                      </地址>
                                                                                                  </客户>
                                                                                                  <地址id="55355">
                                                                                                      <街道>123 主要</街道>
                                                                                                      <城市>旧金山</城市>
                                                                                                      <州>加利福尼亚州</州>
                                                                                                      <邮政编码>94123</邮政编码>
                                                                                                      <国家>美国</国家>
                                                                                                      <手机类型=“手机”>
                                                                                                          <区域>415</区域>
                                                                                                          <前缀>555</前缀>
                                                                                                          <号码>1234</号码>
                                                                                                      </电话>
                                                                                                      <电话类型=“家庭”>
                                                                                                          <区域>415</区域>
                                                                                                          <前缀>555</前缀>
                                                                                                          <号码>5678</号码>
                                                                                                      </电话>
                                                                                                  </地址>
                                                                                                  <地址id="77889">
                                                                                                      <公司>ThoughtWorks</公司>
                                                                                                      <街>汤森410号</街>
                                                                                                      <城市>旧金山</城市>
                                                                                                      <州>加利福尼亚州</州>
                                                                                                      <邮政编码>94107</邮政编码>
                                                                                                      <国家>美国</国家>
                                                                                                  </地址>
                                                                                              </数据>
                                                                                              
                                                                                              <data>
                                                                                                  <cus­tomer>
                                                                                                      <first­name>Joe</first­name>
                                                                                                      <last­name>Doe</last­name>
                                                                                                      <ad­dress type="primary">
                                                                                                          <ref id="55355"/>
                                                                                                      </ad­dress>
                                                                                                      <ad­dress type="sec­ond­ary">
                                                                                                          <ref id="77889"/>
                                                                                                      </ad­dress>
                                                                                                  </cus­tomer>
                                                                                                  <ad­dress id="55355">
                                                                                                      <street>123 Main</street>
                                                                                                      <city>San Fran­cisco</city>
                                                                                                      <state>CA</state>
                                                                                                      <postalcode>94123</postalcode>
                                                                                                      <coun­try>USA</coun­try>
                                                                                                      <phone type="cell">
                                                                                                          <area>415</area>
                                                                                                          <prefix>555</prefix>
                                                                                                          <number>1234</number>
                                                                                                      </phone>
                                                                                                      <phone type="home">
                                                                                                          <area>415</area>
                                                                                                          <prefix>555</prefix>
                                                                                                          <number>5678</number>
                                                                                                      </phone>
                                                                                                  </ad­dress>
                                                                                                  <ad­dress id="77889">
                                                                                                      <com­pany>Thought­Works</com­pany>
                                                                                                      <street>410 Town­send</street>
                                                                                                      <city>San Fran­cisco</city>
                                                                                                      <state>CA</state>
                                                                                                      <postalcode>94107</postalcode>
                                                                                                      <coun­try>USA</coun­try>
                                                                                                  </ad­dress>
                                                                                              </data>
                                                                                              

                                                                                              该 XML 文档包含客户数据。每个客户可以与多个地址相关联,每个地址可以包含多个电话号码。XML 将地址表示为独立的实体,以便多个客户可以共享一个地址。

                                                                                              This XML doc­u­ment con­tains cus­tomer data. Each cus­tomer can be as­so­ci­ated with mul­tiple ad­dresses, each of which can con­tain mul­tiple phone num­bers. The XML rep­res­ents ad­dresses as in­de­pend­ent en­tit­ies so that mul­tiple cus­tom­ers could share an ad­dress.

                                                                                              我们假设会计系统需要以下表示。(如果您认为德语标签名称有点牵强,请记住,最受欢迎的企业软件 (SAP) 之一以其德语字段名称而闻名!)

                                                                                              Let's assume the ac­count­ing system needs the fol­low­ing rep­res­ent­a­tion. (If you think that the German tag names are bit far­fetched, keep in mind that one of the most pop­u­lar pieces of en­ter­prise soft­ware (SAP) is famous for its German field names!)

                                                                                              <昆德>
                                                                                                  <姓名>乔·多伊</姓名>
                                                                                                  <地址>
                                                                                                      <大街>123号大街</大街>
                                                                                                      <Ort>旧金山</Ort>
                                                                                                      <电话>415-555-1234</电话>
                                                                                                  </地址>
                                                                                              </昆德>
                                                                                              
                                                                                              <Kunde>
                                                                                                  <Name>Joe Doe</Name>
                                                                                                  <Ad­resse>
                                                                                                      <Strasse>123 Main</Strasse>
                                                                                                      <Ort>San Fran­cisco</Ort>
                                                                                                      <Tele­fon>415-555-1234</Tele­fon>
                                                                                                  </Ad­resse>
                                                                                              </Kunde>
                                                                                              

                                                                                              生成的文档结构更加简单。标签名称不同,并且某些字段合并为单个字段。由于只有一个地址和电话号码,我们需要根据业务规则从原始文档中选择一个。以下 XSLT 程序将原始文档转换为所需的格式。它通过匹配传入文档的元素并将它们转换为所需的文档格式来实现这一点。

                                                                                              The res­ult­ing doc­u­ment has a much sim­pler struc­ture. Tag names are dif­fer­ent, and some fields are merged into a single field. Since there is room for only one ad­dress and phone number, we need to pick one from the ori­ginal doc­u­ment based on busi­ness rules. The fol­low­ing XSLT pro­gram trans­forms the ori­ginal doc­u­ment into the de­sired format. It does so by match­ing ele­ments of the in­com­ing doc­u­ment and trans­lat­ing them into the de­sired doc­u­ment format.

                                                                                              
                                                                                              <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999
                                                                                              图形/ccc.gif/XSL/转换">
                                                                                                  <xsl:输出方法=“xml”缩进=“是”/>
                                                                                                  <xsl:key name="addrlookup" match="/data/address" use="@id"/>
                                                                                                  <xsl:模板匹配=“数据”>
                                                                                                      <xsl:apply-templates select="客户"/>
                                                                                                  </xsl:模板>
                                                                                                  <xsl:模板匹配=“客户”>
                                                                                                      <昆德>
                                                                                                          <名称>
                                                                                                              <xsl:value-of select="concat(名字, ' ',
                                                                                              图形/ccc.gif姓氏)​​"/>
                                                                                                          </名称>
                                                                                                          <地址>
                                                                                                              <xsl:变量名称=“id”选择=“。
                                                                                              图形/ccc.gif/地址[@type='primary']/ref/@id"/>
                                                                                                              <xsl:call-template name="getaddr">
                                                                                                                  <xsl:with-param name="addr" select="key
                                                                                              图形/ccc.gif('addrlookup', $id)"/>
                                                                                                              </xsl:调用模板>
                                                                                                          </地址>
                                                                                                      </昆德>
                                                                                                  </xsl:模板>
                                                                                                  <xsl:模板名称=“getaddr”>
                                                                                                      <xsl:param name="addr"/>
                                                                                                      <大街>
                                                                                                          <xsl:value-of select="$addr/street"/>
                                                                                                      </大街>
                                                                                                      <奥尔特>
                                                                                                          <xsl:value-of select="$addr/city"/>
                                                                                                      </Ort>
                                                                                                      <电话>
                                                                                                          <xsl:选择>
                                                                                                              <xsl:when test="$addr/phone[@type='cell']">
                                                                                                                  <xsl:apply-templates select="$addr
                                                                                              图形/ccc.gif/phone[@type='cell']" mode="getphone"/>
                                                                                                              </xsl:何时>
                                                                                              
                                                                                                              <xsl:否则>
                                                                                                                  <xsl:apply-templates select="$addr
                                                                                              图形/ccc.gif/phone[@type='home']" mode="getphone"/>
                                                                                                              </xsl:否则>
                                                                                                          </xsl:选择>
                                                                                                      </电话>
                                                                                                  </xsl:模板>
                                                                                                  <xsl:template match="phone" mode="getphone">
                                                                                                      <xsl:value-of select="concat(区域, '-', 前缀, '-',
                                                                                              图形/ccc.gif数)"/>
                                                                                                  </xsl:模板>
                                                                                                  <xsl:模板匹配="*"/>
                                                                                              </xsl:样式表>
                                                                                              
                                                                                              
                                                                                              <xsl:stylesheet ver­sion="1.0" xmlns:xsl="http://www.w3.org/1999
                                                                                              /XSL/Trans­form">
                                                                                                  <xsl:output method="xml" indent="yes"/>
                                                                                                  <xsl:key name="ad­drlookup" match="/data/ad­dress" use="@id"/>
                                                                                                  <xsl:tem­plate match="data">
                                                                                                      <xsl:apply-tem­plates select="cus­tomer"/>
                                                                                                  </xsl:tem­plate>
                                                                                                  <xsl:tem­plate match="cus­tomer">
                                                                                                      <Kunde>
                                                                                                          <Name>
                                                                                                              <xsl:value-of select="concat(first­name, ' ',
                                                                                               last­name)"/>
                                                                                                          </Name>
                                                                                                          <Ad­resse>
                                                                                                              <xsl:vari­able name="id" select=".
                                                                                              /ad­dress[@type='primary']/ref/@id"/>
                                                                                                              <xsl:call-tem­plate name="get­addr">
                                                                                                                  <xsl:with-param name="addr" select="key
                                                                                              ('ad­drlookup', $id)"/>
                                                                                                              </xsl:call-tem­plate>
                                                                                                          </Ad­resse>
                                                                                                      </Kunde>
                                                                                                  </xsl:tem­plate>
                                                                                                  <xsl:tem­plate name="get­addr">
                                                                                                      <xsl:param name="addr"/>
                                                                                                      <Strasse>
                                                                                                          <xsl:value-of select="$addr/street"/>
                                                                                                      </Strasse>
                                                                                                      <Ort>
                                                                                                          <xsl:value-of select="$addr/city"/>
                                                                                                      </Ort>
                                                                                                      <Tele­fon>
                                                                                                          <xsl:choose>
                                                                                                              <xsl:when test="$addr/phone[@type='cell']">
                                                                                                                  <xsl:apply-tem­plates select="$addr
                                                                                              /phone[@type='cell']" mode="get­phone"/>
                                                                                                              </xsl:when>
                                                                                              
                                                                                                              <xsl:oth­er­wise>
                                                                                                                  <xsl:apply-tem­plates select="$addr
                                                                                              /phone[@type='home']" mode="get­phone"/>
                                                                                                              </xsl:oth­er­wise>
                                                                                                          </xsl:choose>
                                                                                                      </Tele­fon>
                                                                                                  </xsl:tem­plate>
                                                                                                  <xsl:tem­plate match="phone" mode="get­phone">
                                                                                                      <xsl:value-of select="concat(area, '-', prefix, '-',
                                                                                               number)"/>
                                                                                                  </xsl:tem­plate>
                                                                                                  <xsl:tem­plate match="*"/>
                                                                                              </xsl:stylesheet>
                                                                                              

                                                                                              XSL 基于模式匹配,如果您像我们大多数人一样习惯过程编程,那么读起来可能有点麻烦。简而言之,每当传入 XML 文档中的元素与match属性中指定的表达式匹配时,就会调用<xsl:template>元素内的指令。例如,该行

                                                                                              XSL is based on pat­tern match­ing and can be a bit hairy to read if you are used to pro­ced­ural pro­gram­ming like most of us. In a nut­shell, the in­struc­tions inside an <xsl:tem­plate> ele­ment are called whenever an ele­ment in the in­com­ing XML doc­u­ment matches the ex­pres­sion spe­cified in the match at­trib­ute. For ex­ample, the line

                                                                                              <xsl:模板匹配=“客户”>
                                                                                              
                                                                                              <xsl:tem­plate match="cus­tomer">
                                                                                              

                                                                                              导致对源文档中的每个<customer>元素执行后续行。接下来的语句连接名字和姓氏并将其输出到<Name>元素内。获取地址有点棘手。XSL 代码查找<address>元素的正确实例并调用子例程getaddr getaddr 从原始<address>元素中提取地址和电话号码。如果有的话,它使用手机号码,否则使用家庭电话号码。

                                                                                              causes the sub­se­quent lines to be ex­ecuted for each <cus­tomer> ele­ment in the source doc­u­ment. The next state­ments con­cat­en­ate first and last name and output it inside the <Name> ele­ment. Get­ting the ad­dress is a little trick­ier. The XSL code looks up the cor­rect in­stance of the <ad­dress> ele­ment and calls the sub­routine get­addr. get­addr ex­tracts the ad­dress and phone number from the ori­ginal <ad­dress> ele­ment. It uses the cell phone number if one is present, or the home phone number oth­er­wise.



                                                                                              示例: 视觉转换工具

                                                                                              Ex­ample: Visual Trans­form­a­tion Tools

                                                                                              如果您发现 XSL 编程有点神秘,那么您有很好的伙伴。因此,大多数集成供应商提供了可视化转换编辑器,分别在屏幕的左侧和右侧显示两种文档格式的结构。然后,用户可以通过在格式之间绘制连接线来将格式之间的元素关联起来。这比编码 XSL 简单得多。一些供应商(例如 Contivo)完全专注于转换工具。

                                                                                              If you find XSL pro­gram­ming a bit cryptic, you are in good com­pany. There­fore, most in­teg­ra­tion vendors provide a visual trans­form­a­tion editor that dis­plays the struc­ture of the two doc­u­ment formats on the left-hand side and the right-hand side of the screen re­spect­ively. The users can then as­so­ci­ate ele­ments between the formats by draw­ing con­nect­ing lines between them. This can be a lot sim­pler than coding XSL. Some vendors, such as Con­tivo, spe­cial­ize en­tirely in trans­form­a­tion tools.

                                                                                              下图显示了集成到 Visual Studio 中的 Microsoft BizTalk Mapper 编辑器。该图比 XSL 脚本更清楚地显示各个元素之间的映射。另一方面,一些细节(例如,如何选择地址)隐藏在所谓的 functoid 图标下方。

                                                                                              The fol­low­ing figure shows the Mi­crosoft BizTalk Mapper editor that is in­teg­rated into Visual Studio. The dia­gram shows the map­ping between in­di­vidual ele­ments more clearly than the XSL script. On the other hand, some of the de­tails (e.g., how the ad­dress is chosen) are hidden un­der­neath the so-called funct­oid icons.

                                                                                              创建转换:拖放样式

                                                                                              Cre­at­ing Trans­form­a­tions: The Drag-Drop Style

                                                                                              图形/03inf13.jpg

                                                                                              能够拖放转换大大缩短了开发消息翻译器的学习曲线。但通常情况下,当涉及到调试或需要创建复杂的解决方案时,可视化工具也可能成为一种负担。因此,许多工具允许您在 XSL 和可视化表示之间来回切换。

                                                                                              Being able to drag and drop trans­form­a­tions shortens the learn­ing curve for de­vel­op­ing a Mes­sage Trans­lator dra­mat­ic­ally. As so often though, visual tools can also become a li­ab­il­ity when it comes to de­bug­ging or when you need to create com­plex solu­tions. There­fore, many tools let you switch back and forth between XSL and the visual rep­res­ent­a­tion.



                                                                                                消息端点

                                                                                                Message Endpoint

                                                                                                图形/messageendpoint_icon.gif

                                                                                                应用程序通过消息发送消息来进行通信。

                                                                                                Ap­plic­a­tions are com­mu­nic­at­ing by send­ing Mes­sages to each other via Mes­sage Chan­nels.

                                                                                                应用程序如何连接到消息通道来发送和接收消息?

                                                                                                How does an ap­plic­a­tion con­nect to a mes­saging chan­nel to send and re­ceive Mes­sages?



                                                                                                应用程序和消息系统是两套独立的软件。应用程序为某种类型的用户提供功能,而消息传递系统管理用于传输消息以进行通信的消息传递通道。即使消息传递系统被合并为应用程序的基本部分,它仍然是一个独立的、专门的功能提供者,很像数据库管理系统或 Web 服务器。由于应用程序和消息传递系统是分开的,因此它们必须有一种连接和协同工作的方式。

                                                                                                The ap­plic­a­tion and the mes­saging system are two sep­ar­ate sets of soft­ware. The ap­plic­a­tion provides func­tion­ally for some type of user, whereas the mes­saging system man­ages mes­saging chan­nels for trans­mit­ting mes­sages for com­mu­nic­a­tion. Even if the mes­saging system is in­cor­por­ated as a fun­da­mental part of the ap­plic­a­tion, it is still a sep­ar­ate, spe­cial­ized pro­vider of func­tion­al­ity, much like a data­base man­age­ment system or a Web server. Be­cause the ap­plic­a­tion and the mes­saging system are sep­ar­ate, they must have a way to con­nect and work to­gether.

                                                                                                应用程序与消息通道断开连接

                                                                                                Ap­plic­a­tions Dis­con­nec­ted from a Mes­sage Chan­nel

                                                                                                图形/03inf14.gif

                                                                                                消息传递系统是一种服务器,能够接受请求并响应它们。就像数据库接受和检索数据一样,消息传递服务器接受和传递消息。消息传递系统是消息传递服务器。

                                                                                                A mes­saging system is a type of server, cap­able of taking re­quests and re­spond­ing to them. Like a data­base ac­cept­ing and re­triev­ing data, a mes­saging server ac­cepts and de­liv­ers mes­sages. A mes­saging system is a mes­saging server.

                                                                                                服务器需要客户端,而使用消息传递的应用程序是消息传递服务器的客户端。但应用程序不一定知道如何成为消息传递客户端,就像它们不一定知道如何成为数据库客户端一样。消息传递服务器与数据库服务器一样,具有客户端 API,应用程序可以使用该 API 与服务器进行交互。API 不是特定于应用程序的,而是特定于域的,其中域是消息传递的。应用程序必须包含一组将消息传递域与应用程序连接和统一的代码,以允许应用程序执行消息传递。

                                                                                                A server needs cli­ents, and an ap­plic­a­tion that uses mes­saging is a client of the mes­saging server. But ap­plic­a­tions do not ne­ces­sar­ily know how to be mes­saging cli­ents any more than they know how to be data­base cli­ents. The mes­saging server, like a data­base server, has a client API that the ap­plic­a­tion can use to in­ter­act with the server. The API is not ap­plic­a­tion-spe­cific but is domain-spe­cific, where the domain is mes­saging. The ap­plic­a­tion must con­tain a set of code that con­nects and unites the mes­saging domain with the ap­plic­a­tion to allow the ap­plic­a­tion to per­form mes­saging.

                                                                                                使用消息端点将应用程序连接到消息传递通道,消息端点是消息传递系统的客户端,应用程序可以使用它来发送或接收消息。

                                                                                                Con­nect an ap­plic­a­tion to a mes­saging chan­nel using a Mes­sage En­d­point, a client of the mes­saging system that the ap­plic­a­tion can then use to send or re­ceive Mes­sages.

                                                                                                图形/03inf15.gif



                                                                                                消息端点代码是应用程序和消息系统的客户端 API 自定义的。应用程序的其余部分对消息格式、消息传递通道或通过消息传递与其他应用程序进行通信的任何其他细节知之甚少。它只知道它有一个请求或数据要发送到另一个应用程序,或者期待来自另一个应用程序的请求或数据。消息传送端点代码接收该命令或数据,将其制作成消息,并将其发送到特定的消息传送通道。它是接收消息、提取内容并以有意义的方式将其提供给应用程序的端点。

                                                                                                Mes­sage En­d­point code is custom to both the ap­plic­a­tion and the mes­saging system's client API. The rest of the ap­plic­a­tion knows little about mes­sage formats, mes­saging chan­nels, or any of the other de­tails of com­mu­nic­at­ing with other ap­plic­a­tions via mes­saging. It just knows that it has a re­quest or piece of data to send to an­other ap­plic­a­tion, or is ex­pect­ing those from an­other ap­plic­a­tion. It is the mes­saging en­d­point code that takes that com­mand or data, makes it into a mes­sage, and sends it on a par­tic­u­lar mes­saging chan­nel. It is the en­d­point that re­ceives a mes­sage, ex­tracts the con­tents, and gives them to the ap­plic­a­tion in a mean­ing­ful way.

                                                                                                消息端点将消息传递系统与应用程序的其余部分封装在一起,并为特定应用程序和任务定制通用消息传递 API。如果使用特定消息 API 的应用程序要切换到另一个,开发人员将必须重写消息端点代码,但应用程序的其余部分应保持不变。如果新版本的消息传递系统更改了消息传递 API,则这只会影响消息端点代码。如果应用程序决定通过消息传递以外的某种方式与其他人进行通信,那么开发人员理想情况下应该能够重写消息端点代码,但保持应用程序的其余部分不变。

                                                                                                The Mes­sage En­d­point en­cap­su­lates the mes­saging system from the rest of the ap­plic­a­tion and cus­tom­izes a gen­eral mes­saging API for a spe­cific ap­plic­a­tion and task. If an ap­plic­a­tion using a par­tic­u­lar mes­saging API were to switch to an­other, de­ve­lopers would have to re­write the mes­sage en­d­point code, but the rest of the ap­plic­a­tion should remain the same. If a new ver­sion of a mes­saging system changes the mes­saging API, this should only affect the mes­sage en­d­point code. If the ap­plic­a­tion de­cides to com­mu­nic­ate with others via some means other than mes­saging, de­ve­lopers should ideally be able to re­write the mes­sage en­d­point code but leave the rest of the ap­plic­a­tion un­changed.

                                                                                                消息端点可用于发送或接收消息,但一个实例不能同时执行这两种操作。端点是特定于通道的,因此单个应用程序将使用多个端点与多个通道进行交互。应用程序可以使用多个端点实例来连接到单个通道,通常是为了支持多个并发线程。

                                                                                                A Mes­sage En­d­point can be used to send mes­sages or re­ceive them, but one in­stance does not do both. An en­d­point is chan­nel-spe­cific, so a single ap­plic­a­tion would use mul­tiple en­d­points to in­ter­face with mul­tiple chan­nels. An ap­plic­a­tion may use mul­tiple en­d­point in­stances to in­ter­face to a single chan­nel, usu­ally to sup­port mul­tiple con­cur­rent threads.

                                                                                                消息端点是一种专门的通道适配器,是为其应用程序定制开发并集成到其应用程序中的。

                                                                                                A Mes­sage En­d­point is a spe­cial­ized Chan­nel Ad­apter one that has been custom de­ve­loped for and in­teg­rated into its ap­plic­a­tion.

                                                                                                消息端点应设计为消息传递网关,以封装消息传递代码并向应用程序的其余部分隐藏消息系统。它可以使用消息传递映射器在域对象和消息之间传输数据。它可以被构造为服务激活器,以提供对同步服务或函数调用的异步消息访问。端点可以作为事务客户端显式地控制与消息传递系统的事务。

                                                                                                A Mes­sage En­d­point should be de­signed as a Mes­saging Gate­way to en­cap­su­late the mes­saging code and hide the mes­sage system from the rest of the ap­plic­a­tion. It can employ a Mes­saging Mapper to trans­fer data between domain ob­jects and mes­sages. It can be struc­tured as a Ser­vice Ac­tiv­ator to provide asyn­chron­ous mes­sage access to a syn­chron­ous ser­vice or func­tion call. An en­d­point can ex­pli­citly con­trol trans­ac­tions with the mes­saging system as a Trans­ac­tional Client.

                                                                                                发送消息非常容易,因此许多端点模式涉及接收消息的不同方法。消息接收者可以是轮询消费者或事件驱动消费者。多个消费者可以作为竞争消费者或通过消息调度程序从同一通道接收消息。接收者可以使用Selective Consumer 来决定使用或忽略哪些消息。它可以使用持久订阅者来确保订阅者不会错过端点断开连接时发布的消息。消费者可以是幂等接收者正确检测并处理重复消息。

                                                                                                Send­ing mes­sages is pretty easy, so many en­d­point pat­terns con­cern dif­fer­ent ap­proaches for re­ceiv­ing mes­sages. A mes­sage re­ceiver can be a Polling Con­sumer or an Event-Driven Con­sumer. Mul­tiple con­sumers can re­ceive mes­sages from the same chan­nel either as Com­pet­ing Con­sumers or via a Mes­sage Dis­patcher. A re­ceiver can decide which mes­sages to con­sume or ignore using a Se­lect­ive Con­sumer. It can use a Dur­able Sub­scriber to make sure a sub­scriber does not miss mes­sages pub­lished while the en­d­point is dis­con­nec­ted. And the con­sumer can be an Idem­potent Re­ceiver that cor­rectly de­tects and handles du­plic­ate mes­sages.

                                                                                                示例: JMS 生产者和消费者

                                                                                                Ex­ample: JMS Pro­du­cer and Con­sumer

                                                                                                在 JMS 中,两个主要端点类型是用于发送和用于接收 。消息端点使用这些类型之一的实例向特定通道发送消息或从特定通道接收消息。

                                                                                                In JMS, the two main en­d­point types are Mes­sage­Pro­du­cer, for send­ing mes­sages, and Mes­sage­Con­sumer, for re­ceiv­ing mes­sages. A Mes­sage En­d­point uses an in­stance of one of these types to either send mes­sages to or re­ceive mes­sages from a par­tic­u­lar chan­nel.



                                                                                                示例: .NET 消息队列

                                                                                                Ex­ample: .NET Mes­sageQueue

                                                                                                在 .NET 中,主端点类与主消息通道类MessageQueue相同。 消息端点使用MessageQueue的实例向特定通道发送消息或从特定通道接收消息。

                                                                                                In .NET, the main en­d­point class is the same as the main Mes­sage Chan­nel class, Mes­sageQueue. A Mes­sage En­d­point uses an in­stance of Mes­sageQueue to send mes­sages to or re­ceive mes­sages from a par­tic­u­lar chan­nel.



                                                                                                  介绍

                                                                                                  Introduction

                                                                                                  第 3 章“消息系统”中,我们讨论了消息通道。当两个应用程序想要交换数据时,它们通过连接两个应用程序的通道发送数据来实现。发送数据的应用程序可能不知道哪个应用程序将接收数据。然而,通过选择发送数据的特定通道,发送方知道接收方将通过在该通道上查找来寻找此类数据。通过这种方式,生成共享数据的应用程序可以与那些希望使用该数据的应用程序进行通信。

                                                                                                  In Chapter 3, "Mes­saging Sys­tems," we dis­cussed Mes­sage Chan­nels. When two ap­plic­a­tions want to ex­change data, they do so by send­ing the data through a chan­nel that con­nects the two. The ap­plic­a­tion send­ing the data may not know which ap­plic­a­tion will re­ceive the data. How­ever, by se­lect­ing a par­tic­u­lar chan­nel on which to send the data, the sender knows that the re­ceiver will be one that is look­ing for that sort of data by look­ing for it on that chan­nel. In this way, the ap­plic­a­tions that pro­duce shared data have a way to com­mu­nic­ate with those that wish to con­sume it.

                                                                                                  消息频道主题

                                                                                                  Mes­sage Chan­nel Themes

                                                                                                  决定使用消息通道是很简单的部分;如果应用程序有数据要传输或希望接收数据,则它必须使用通道。挑战在于了解您的应用程序需要哪些渠道以及它们的用途。

                                                                                                  De­cid­ing to use a Mes­sage Chan­nel is the simple part; if an ap­plic­a­tion has data to trans­mit or data it wishes to re­ceive, it will have to use a chan­nel. The chal­lenge is know­ing what chan­nels your ap­plic­a­tions will need and what to use them for.

                                                                                                  固定通道集 本章讨论的 一个主题是消息通道集应用程序可用的内容往往是静态的。在设计应用程序时,开发人员必须知道在哪里放置什么类型的数据以便与其他应用程序共享该数据,以及在哪里查找来自其他应用程序的特定类型的数据。这些通信路径无法在运行时动态创建和发现;它们需要在设计时达成一致,以便应用程序知道其数据来自何处以及数据将去往何处。(虽然大多数通道确实必须静态定义,但也有例外,在这种情况下动态通道是实用且有用的。一个例外是请求-应答中的应答通道。请求者可以创建或获取回复者一无所知的新通道,并将其指定为请求消息的返回地址。然后回复者就可以使用它。另一个例外是支持分层通道的消息传递系统实现。接收者可以订阅层次结构中的父通道,然后发送者可以发布到接收者一无所知的新子通道。订阅者仍会收到该消息。尽管存在这些相对不寻常的情况,但通道通常是在部署之前定义的,并且应用程序是围绕一组已知的通道设计的。)

                                                                                                  确定通道集 一个 相关的问题是,谁决定什么消息通道消息系统或应用程序可用吗?换句话说,消息传递系统是否定义了某些通道并要求应用程序使用这些通道?或者应用程序是否确定它们需要什么通道并要求消息系统提供它们?没有简单的答案;设计所需的通道集是迭代的。首先,应用程序确定消息系统需要提供哪些通道。后续应用程序将尝试围绕可用通道设计通信,但当这不切实际时,它们将要求添加额外的通道。当一组应用程序已经使用一组特定的通道并且新的应用程序想要加入时,它们也将使用现有的一组通道。

                                                                                                  单向通道 另一个常见的混淆来源是消息通道是否是单向或双向的。从技术上讲,两者都不是;通道更像是一个存储桶,一些应用程序向其中添加数据,其他应用程序从中获取数据(尽管存储桶以某种协调的方式分布在多台计算机上)。但由于数据位于从一个应用程序传输到另一个应用程序的消息中,因此这就给出了通道方向,使其成为单向的。如果通道是双向的,则意味着应用程序将向同一通道发送消息并从同一通道接收消息,这虽然在技术上可能没有什么意义,因为应用程序往往会继续消耗自己的消息:它应该发送到的消息其他应用程序。因此,出于所有实际目的,通道是单向的。因此,要使两个应用程序进行双向对话,它们需要两个通道,每个方向一个(请参阅请求-答复将在下一章中介绍)。

                                                                                                  Fixed set of chan­nels One topic dis­cussed in this chapter is that the set of Mes­sage Chan­nels avail­able to an ap­plic­a­tion tends to be static. When design­ing an ap­plic­a­tion, a de­ve­loper must know where to put what types of data in order to share that data with other ap­plic­a­tions and like­wise where to look for par­tic­u­lar types of data coming from other ap­plic­a­tions. These paths of com­mu­nic­a­tion cannot be dy­nam­ic­ally cre­ated and dis­covered at runtime; they need to be agreed upon at design time so that the ap­plic­a­tion knows where its data is coming from and where the data is going to. (While it is true that most chan­nels must be stat­ic­ally defined, there are ex­cep­tions, cases where dy­namic chan­nels are prac­tical and useful. One ex­cep­tion is the reply chan­nel in Re­quest-Reply. The re­questor can create or obtain a new chan­nel that the replier knows noth­ing about and spe­cify it as the Return Ad­dress of a re­quest mes­sage. The replier can then use it. An­other ex­cep­tion is mes­saging system im­ple­ment­a­tions that sup­port hier­arch­ical chan­nels. A re­ceiver can sub­scribe to a parent in the hier­archy, and then a sender can pub­lish to a new child chan­nel that the re­ceiver knows noth­ing about. The sub­scriber will still re­ceive the mes­sage. These re­l­at­ively un­usual cases not­with­stand­ing, chan­nels are usu­ally defined before de­ploy­ment, and ap­plic­a­tions are de­signed around a known set of chan­nels.)

                                                                                                  De­term­in­ing the set of chan­nels A re­lated issue is, Who de­cides what Mes­sage Chan­nels are avail­ab­lethe mes­saging system or the ap­plic­a­tions? In other words, does the mes­saging system define cer­tain chan­nels and re­quire the ap­plic­a­tions to make do with those? Or do the ap­plic­a­tions de­term­ine what chan­nels they need and re­quire the mes­saging system to provide them? There is no simple answer; design­ing the needed set of chan­nels is it­er­at­ive. First, the ap­plic­a­tions de­term­ine which chan­nels the mes­saging system needs to provide. Sub­se­quent ap­plic­a­tions will try to design their com­mu­nic­a­tion around the chan­nels that are avail­able, but when this is not prac­tical, they will re­quire that ad­di­tional chan­nels be added. When a set of ap­plic­a­tions already uses a cer­tain set of chan­nels, and new ap­plic­a­tions want to join in, they too will use the ex­ist­ing set of chan­nels. When ex­ist­ing ap­plic­a­tions add new func­tion­al­ity, they may re­quire new chan­nels.

                                                                                                  Uni­direc­tional chan­nels An­other common source of con­fu­sion is whether a Mes­sage Chan­nel is uni­direc­tional or bi­d­irec­tional. Tech­nic­ally, it's neither; a chan­nel is more like a bucket that some ap­plic­a­tions add data to and other ap­plic­a­tions take data from (albeit a bucket that is dis­trib­uted across mul­tiple com­puters in some co­ordin­ated fash­ion). But be­cause the data is in mes­sages that travel from one ap­plic­a­tion to an­other, that gives the chan­nel dir­ec­tion, making it uni­direc­tional. If a chan­nel were bi­d­irec­tional, that would mean that an ap­plic­a­tion would both send mes­sages to and re­ceive mes­sages from the same chan­nel, which­while tech­nic­ally pos­sible­makes little sense be­cause the ap­plic­a­tion would tend to keep con­sum­ing its own mes­sages: the mes­sages it's sup­posed to be send­ing to other ap­plic­a­tions. So, for all prac­tical pur­poses, chan­nels are uni­direc­tional. As a con­se­quence, for two ap­plic­a­tions to have a two-way con­ver­sa­tion, they need two chan­nels, one in each dir­ec­tion (see Re­quest-Reply in the next chapter).

                                                                                                  消息渠道决策

                                                                                                  Mes­sage Chan­nel De­cisions

                                                                                                  现在我们了解了消息通道是什么,让我们考虑使用它们所涉及的决策。

                                                                                                  Now that we un­der­stand what Mes­sage Chan­nels are, let's con­sider the de­cisions in­volved in using them.

                                                                                                  一对一或一对多 当您的应用程序共享一段数据时,您希望仅与一个其他应用程序还是与任何其他感兴趣的应用程序共享它?要将数据发送到单个应用程序,请使用点对点通道。这并不能保证在该通道上发送的每条数据都一定会发送到同一个接收器,因为该通道可能有多个接收器。然而,它确实确保任何一份数据仅被一个应用程序接收。如果您希望所有接收器应用程序都能够接收数据,请使用Publish-Subscribe Channel 。当您以这种方式发送一段数据时,通道会有效地为每个接收者复制数据。

                                                                                                  什么类型的数据 任何计算机内存中的任何数据都必须符合某种类型具有商定含义的已知格式或预期结构。否则,所有数据都只是一堆字节,并且无法理解它。消息系统的工作方式大致相同;消息内容必须符合某种类型,以便接收者理解数据的结构。数据类型通道的理念是通道上的所有数据必须属于同一类型。这是消息传递系统需要大量通道的主要原因。如果数据可以是任何类型,则消息传递系统在任意两个应用程序之间只需要一个通道(在每个方向)。

                                                                                                  无效消息和 死消息消息系统可以确保消息正确传递,但不能保证接收者知道如何处理它。接收者对数据的类型和含义有期望。当它收到不符合这些期望的消息时,它无能为力。不过,它可以做的就是将奇怪的消息放在专门指定的无效消息通道上,希望监视该通道的某些实用程序能够拾取该消息并找出如何处理它。许多消息传递系统都有类似的内置功能,即死信通道,对于已成功发送但最终无法成功投递的消息。同样,系统管理实用程序应该监视死信通道并决定如何处理无法传递的消息。

                                                                                                  防崩溃 如果消息系统崩溃或关闭进行维护,其消息会发生什么情况?当它恢复并运行时,它的消息仍然在它的通道中吗?默认情况下,否;通道将其消息存储在内存中。然而,保证交付使通道持久化,以便它们的消息存储在磁盘上。这会损害性能,但会使消息传递更加可靠,即使消息传递系统并不可靠。

                                                                                                  非消息传递客户端 如果应用程序无法连接到消息传递系统但仍想参与消息传递 怎么办?通常情况下,这会很不幸,但如果消息传递系统可以通过其用户界面、业务服务 API、数据库或网络连接(例如 TCP/IP 或 HTTP)以某种方式连接到应用程序,那么通道适配器就可以使用消息系统。这允许您将一个通道(或一组通道)连接到应用程序,而无需修改应用程序,并且可能不需要消息客户端与应用程序在同一台计算机上运行。有时,“非消息传递客户端”确实是消息传递客户端,但只是针对不同的消息传递系统。在这种情况下,作为两个消息传递系统上的客户端的应用程序可以在两者之间构建消息传递桥,从而有效地将它们连接到一个复合消息传递系统中。

                                                                                                  通信主干 随着 越来越多的企业应用程序连接到消息传递系统并通过消息传递提供其功能,消息传递系统成为企业中共享功能的一站式采购的集中点。新应用程序只需要知道使用哪些通道来请求功能以及监听哪些其他通道来获取结果。消息传递系统本身本质上成为消息总线,它是提供对企业所有各种且不断变化的应用程序和功能的访问的主干网。通过从一开始就专门设计,您可以更快、更轻松地实现这种集成必杀技。

                                                                                                  One-to-one or one-to-many When your ap­plic­a­tion shares a piece of data, do you want to share it with just one other ap­plic­a­tion or with any other ap­plic­a­tion that is in­ter­ested? To send the data to a single ap­plic­a­tion, use a Point-to-Point Chan­nel. This does not guar­an­tee that every piece of data sent on that chan­nel will ne­ces­sar­ily go to the same re­ceiver, be­cause the chan­nel might have mul­tiple re­ceiv­ers. It does, how­ever, ensure that any one piece of data will be re­ceived by only one of the ap­plic­a­tions. If you want all of the re­ceiver ap­plic­a­tions to be able to re­ceive the data, use a Pub­lish-Sub­scribe Chan­nel. When you send a piece of data this way, the chan­nel ef­fect­ively copies the data for each of the re­ceiv­ers.

                                                                                                  What type of data Any data in any com­puter memory has to con­form to some sort of type: a known format or ex­pec­ted struc­ture with an agreed-upon mean­ing. Oth­er­wise, all data would just be a bunch of bytes, and there would be no way to make any sense out of it. Mes­saging sys­tems work much the same way; the mes­sage con­tents must con­form to some type so that the re­ceiver un­der­stands the data's struc­ture. Data­type Chan­nel is the idea that all of the data on a chan­nel must be of the same type. This is the main reason that mes­saging sys­tems need lots of chan­nels. If the data could be of any type, the mes­saging system would only need one chan­nel (in each dir­ec­tion) between any two ap­plic­a­tions.

                                                                                                  In­valid and dead mes­sages The mes­sage system can ensure that a mes­sage is de­livered prop­erly, but it cannot guar­an­tee that the re­ceiver will know what to do with it. The re­ceiver has ex­pect­a­tions about the data's type and mean­ing. When it re­ceives a mes­sage that doesn't meet these ex­pect­a­tions, there's not much it can do. What it can do, though, is put the strange mes­sage on a spe­cially des­ig­nated In­valid Mes­sage Chan­nel in hopes that some util­ity mon­it­or­ing the chan­nel will pick up the mes­sage and figure out what to do with it. Many mes­saging sys­tems have a sim­ilar built-in fea­ture, a Dead Letter Chan­nel, for mes­sages that are suc­cess­fully sent but ul­ti­mately cannot be suc­cess­fully de­livered. Again, a system man­age­ment util­ity should mon­itor the Dead Letter Chan­nel and decide what to do with the mes­sages that could not be de­livered.

                                                                                                  Crash proof If the mes­saging system crashes or is shut down for main­ten­ance, what hap­pens to its mes­sages? When it is back up and run­ning, will its mes­sages still be in its chan­nels? By de­fault, no; chan­nels store their mes­sages in memory. How­ever, Guar­an­teed De­liv­ery makes chan­nels per­sist­ent so that their mes­sages are stored on disk. This hurts per­form­ance but makes mes­saging more re­li­able, even when the mes­saging system isn't.

                                                                                                  Non-mes­saging cli­ents What if an ap­plic­a­tion cannot con­nect to a mes­saging system but still wants to par­ti­cip­ate in mes­saging? Nor­mally it would be out of luck, but if the mes­saging system can con­nect to the ap­plic­a­tion some­how­through its user in­ter­face, its busi­ness ser­vices API, its data­base, or a net­work con­nec­tion such as TCP/IP or HT­TPthen a Chan­nel Ad­apter on the mes­saging system can be used. This allows you to con­nect a chan­nel (or set of chan­nels) to the ap­plic­a­tion without having to modify the ap­plic­a­tion and per­haps without re­quir­ing a mes­saging client run­ning on the same ma­chine as the ap­plic­a­tion. Some­times the "non-mes­saging client" really is a mes­saging client, but just for a dif­fer­ent mes­saging system. In that case, an ap­plic­a­tion that is a client on both mes­saging sys­tems can build a Mes­saging Bridge between the two, ef­fect­ively con­nect­ing them into one com­pos­ite mes­saging system.

                                                                                                  Com­mu­nic­a­tions back­bone As more and more of an en­ter­prise's ap­plic­a­tions con­nect to the mes­saging system and make their func­tion­al­ity avail­able through mes­saging, the mes­saging system be­comes a cent­ral­ized point of one-stop shop­ping for shared func­tion­al­ity in the en­ter­prise. A new ap­plic­a­tion simply needs to know which chan­nels to use to re­quest func­tion­al­ity and which others to listen on for the res­ults. The mes­saging system itself es­sen­tially be­comes a Mes­sage Bus, a back­bone provid­ing access to all of the en­ter­prise's vari­ous and ever-chan­ging ap­plic­a­tions and func­tion­al­ity. You can achieve this in­teg­ra­tion nir­vana more quickly and easily by spe­cific­ally design­ing for it from the be­gin­ning.

                                                                                                  正如您所看到的,为消息传递设置应用程序不仅仅涉及将它们连接到消息传递系统以便它们可以发送消息。消息必须有消息通道来传输。仅仅在某些渠道上进行打压也无法完成工作。它们的设计必须有一个目的,基于共享的数据类型、提供数据的应用程序类型以及接收数据的应用程序类型。本章解释了设计这些渠道时的决策。

                                                                                                  As you can see, get­ting ap­plic­a­tions set up for Mes­saging in­volves more than just con­nect­ing them to the mes­saging system so that they can send mes­sages. The mes­sages must have Mes­sage Chan­nels to trans­mit on. Simply slap­ping in some chan­nels doesn't get the job done either. They have to be de­signed with a pur­pose, based on the data­type being shared, the sort of ap­plic­a­tion making the data avail­able, and the sort of ap­plic­a­tion re­ceiv­ing the data. This chapter ex­plains the de­cisions that go into design­ing these chan­nels.

                                                                                                  为了帮助说明这些模式,每个模式都提供了一个来自虚构的、简化的股票交易领域的示例。虽然这些示例都不应该用作实现真实交易系统的基础,但它们确实可以作为如何使用模式的简短而具体的示例。

                                                                                                  To help il­lus­trate the pat­terns, each one has an ex­ample from a fic­ti­tious, sim­pli­fied stock trad­ing domain. While none of these ex­amples should be used as the basis for im­ple­ment­ing a real trad­ing system, they do serve as brief and spe­cific ex­amples of how the pat­terns can be used.

                                                                                                    点对点通道

                                                                                                    Point-to-Point Channel

                                                                                                    图形/pointtopoint_icon.gif

                                                                                                    应用程序正在使用消息传递进行远程过程调用 (RPC) 或传输文档。

                                                                                                    An ap­plic­a­tion is using Mes­saging to make remote pro­ced­ure calls (RPCs) or trans­fer doc­u­ments.

                                                                                                    呼叫者如何确定只有一位接收者会收到文档或执行呼叫?

                                                                                                    How can the caller be sure that ex­actly one re­ceiver will re­ceive the doc­u­ment or per­form the call?



                                                                                                    RPC 的优点之一是它是在单个远程进程上调用的,因此接收方要么执行该过程,要么不执行(并且会发生异常)。由于接收方仅被调用一次,因此它仅执行该过程一次。但对于消息传递,一旦呼叫被打包为消息并放置在消息通道上,许多接收者可能会在通道上看到它并决定执行该过程。

                                                                                                    One ad­vant­age of an RPC is that it's in­voked on a single remote pro­cess, so either that re­ceiver per­forms the pro­ced­ure or it does not (and an ex­cep­tion occurs). And since the re­ceiver was called only once, it per­forms the pro­ced­ure only once. But with mes­saging, once a call is pack­aged as a Mes­sage and placed on a Mes­sage Chan­nel, po­ten­tially many re­ceiv­ers could see it on the chan­nel and decide to per­form the pro­ced­ure.

                                                                                                    消息传递系统可以防止多个接收器监视单个通道,但这会不必要地限制希望向多个接收器传输数据的呼叫者。通道上的所有接收器可以进行协调,以确保只有其中一个接收器实际执行该过程,但这会很复杂,会产生大量通信开销,并且通常会增加其他独立接收器之间的耦合。单个通道上的多个接收器可能是理想的,以便可以同时使用多个消息,但任何一个接收器都应该使用任何单个消息。

                                                                                                    The mes­saging system could pre­vent more than one re­ceiver from mon­it­or­ing a single chan­nel, but this would un­ne­ces­sar­ily limit callers that wish to trans­mit data to mul­tiple re­ceiv­ers. All of the re­ceiv­ers on a chan­nel could co­ordin­ate to ensure that only one of them ac­tu­ally per­forms the pro­ced­ure, but that would be com­plex, create a lot of com­mu­nic­a­tions over­head, and gen­er­ally in­crease the coup­ling between oth­er­wise in­de­pend­ent re­ceiv­ers. Mul­tiple re­ceiv­ers on a single chan­nel may be de­sir­able so that mul­tiple mes­sages can be con­sumed con­cur­rently, but any one re­ceiver should con­sume any single mes­sage.

                                                                                                    在点对点通道上发送消息,这可确保只有一个接收者会收到特定消息。

                                                                                                    Send the mes­sage on a Point-to-Point Chan­nel, which en­sures that only one re­ceiver will re­ceive a par­tic­u­lar mes­sage.

                                                                                                    图形/04inf01.gif



                                                                                                    点对点通道确保只有一个接收者消费任何给定的消息。通道可以有多个接收者,这些接收者可以同时消费多条消息,但只有其中一个可以成功消费某一特定消息。如果多个接收者尝试消费一条消息,通道会确保只有其中一个成功,因此接收者不必相互协调。

                                                                                                    A Point-to-Point Chan­nel en­sures that only one re­ceiver con­sumes any given mes­sage. The chan­nel can have mul­tiple re­ceiv­ers that can con­sume mul­tiple mes­sages con­cur­rently, but only one of them can suc­cess­fully con­sume a par­tic­u­lar mes­sage. If mul­tiple re­ceiv­ers try to con­sume a single mes­sage, the chan­nel en­sures that only one of them suc­ceeds, so the re­ceiv­ers do not have to co­ordin­ate with each other.

                                                                                                    点对点通道只有一个消费者时,一条消息仅被消费一次这一事实并不奇怪。当通道有多个消费者时,它们就成为竞争消费者,并且通道确保只有一个消费者接收每条消息。这种设计使得消息的消费和处理具有高度的可扩展性,因为该工作可以在多台计算机上的多个应用程序中运行的多个消费者之间实现负载平衡。

                                                                                                    When a Point-to-Point Chan­nel has only one con­sumer, the fact that a mes­sage gets con­sumed only once is not sur­pris­ing. When the chan­nel has mul­tiple con­sumers, then they become Com­pet­ing Con­sumers, and the chan­nel en­sures that only one of the con­sumers re­ceives each mes­sage. This design makes con­sum­ing and pro­cess­ing mes­sages highly scal­able be­cause that work can be load-bal­anced across mul­tiple con­sumers run­ning in mul­tiple ap­plic­a­tions on mul­tiple com­puters.

                                                                                                    您使用点对点通道仅向其中一个可用接收者发送消息,而要将消息发送至所有可用接收者,请使用发布-订阅通道。要使用消息传递实现 RPC,请使用带有一对点对点 Channel 的Request-Reply。调用是命令消息,回复是文档消息。

                                                                                                    Whereas you use a Point-to-Point Chan­nel to send a mes­sage to only one of the avail­able re­ceiv­ers, to send a mes­sage to all avail­able re­ceiv­ers, use a Pub­lish-Sub­scribe Chan­nel. To im­ple­ment RPCs using mes­saging, use Re­quest-Reply with a pair of Point-to-Point Chan­nels. The call is a Com­mand Mes­sage, and the reply is a Doc­u­ment Mes­sage.

                                                                                                    示例: 股票交易

                                                                                                    Ex­ample: Stock Trad­ing

                                                                                                    在股票交易系统中,进行特定交易的请求是一条消息,应该由一个接收者使用和执行,因此该消息应该放置在点对点通道上

                                                                                                    In a stock trad­ing system, the re­quest to make a par­tic­u­lar trade is a mes­sage that should be con­sumed and per­formed by ex­actly one re­ceiver, so the mes­sage should be placed on a Point-to-Point Chan­nel.



                                                                                                    示例: JMS 队列

                                                                                                    Ex­ample: JMS Queue

                                                                                                    在 JMS 中,点对点通道实现了Queue接口。发送方使用QueueSender发送消息;每个接收者使用自己的QueueReceiver 来接收消息 [ JMS 1.1]、 [ Hapner ] 。

                                                                                                    In JMS, a point-to-point chan­nel im­ple­ments the Queue in­ter­face. The sender uses a QueueSender to send mes­sages; each re­ceiver uses its own QueueRe­ceiver to re­ceive mes­sages [JMS 1.1], [Hapner].

                                                                                                    应用程序使用QueueSender来发送如下消息:

                                                                                                    An ap­plic­a­tion uses a QueueSender to send a mes­sage like this:

                                                                                                    
                                                                                                    Queuequeue = //通过JNDI获取队列
                                                                                                    QueueConnectionFactoryfactory = // 获取连接工厂
                                                                                                    图形/ccc.gif通过 JNDI
                                                                                                    QueueConnection 连接=factory.createQueueConnection();
                                                                                                    QueueSession 会话 = connection.createQueueSession(true,
                                                                                                    图形/ccc.gif会话.AUTO_ACKNOWLEDGE);
                                                                                                    QueueSender 发送者 = session.createSender(queue);
                                                                                                    
                                                                                                    Message message = session.createTextMessage("消息内容
                                                                                                    图形/ccc.gif信息。”);
                                                                                                    
                                                                                                    发送者.发送(消息);
                                                                                                    
                                                                                                    
                                                                                                    Queue queue = // obtain the queue via JNDI
                                                                                                    QueueCon­nec­tion­Fact­ory fact­ory = // obtain the con­nec­tion fact­ory
                                                                                                     via JNDI
                                                                                                    QueueCon­nec­tion con­nec­tion = fact­ory.cre­ateQueueCon­nec­tion();
                                                                                                    QueueSes­sion ses­sion = con­nec­tion.cre­ateQueueSes­sion(true,
                                                                                                     Ses­sion.AUTO_AC­KNOW­LEDGE);
                                                                                                    QueueSender sender = ses­sion.cre­ateSender(queue);
                                                                                                    
                                                                                                    Mes­sage mes­sage = ses­sion.cre­at­e­Text­Mes­sage("The con­tents of the
                                                                                                     mes­sage.");
                                                                                                    
                                                                                                    sender.send(mes­sage);
                                                                                                    

                                                                                                    应用程序使用QueueReceiver 来接收如下消息:

                                                                                                    An ap­plic­a­tion uses a QueueRe­ceiver to re­ceive a mes­sage like this:

                                                                                                    
                                                                                                    Queuequeue = //通过JNDI获取队列
                                                                                                    QueueConnectionFactoryfactory = // 获取连接工厂
                                                                                                    图形/ccc.gif通过 JNDI
                                                                                                    QueueConnection 连接=factory.createQueueConnection();
                                                                                                    QueueSession 会话 = connection.createQueueSession(true,
                                                                                                    图形/ccc.gif会话.AUTO_ACKNOWLEDGE);
                                                                                                    QueueReceiver 接收者 = session.createReceiver(queue);
                                                                                                    
                                                                                                    TextMessage 消息 = (TextMessage)receiver.receive();
                                                                                                    字符串内容 = message.getText();
                                                                                                    
                                                                                                    
                                                                                                    Queue queue = // obtain the queue via JNDI
                                                                                                    QueueCon­nec­tion­Fact­ory fact­ory = // obtain the con­nec­tion fact­ory
                                                                                                     via JNDI
                                                                                                    QueueCon­nec­tion con­nec­tion = fact­ory.cre­ateQueueCon­nec­tion();
                                                                                                    QueueSes­sion ses­sion = con­nec­tion.cre­ateQueueSes­sion(true,
                                                                                                     Ses­sion.AUTO_AC­KNOW­LEDGE);
                                                                                                    QueueRe­ceiver re­ceiver = ses­sion.creat­eR­eceiver(queue);
                                                                                                    
                                                                                                    Text­Mes­sage mes­sage = (Text­Mes­sage) re­ceiver.re­ceive();
                                                                                                    String con­tents = mes­sage.get­Text();
                                                                                                    

                                                                                                    注意:JMS 1.1 统一了点对点和发布-订阅域的客户端 API,因此此处显示的代码可以简化为使用Destination 、 ConnectionFactory 、 Connection 、 SessionMessageProducerMessageConsumer 而不是特定于队列的对应项

                                                                                                    NOTE: JMS 1.1 uni­fies the client APIs for the point-to-point and pub­lish-sub­scribe do­mains, so the code shown here can be sim­pli­fied to use Des­tin­a­tion, Con­nec­tion­Fact­ory, Con­nec­tion, Ses­sion, Mes­sage­Pro­du­cer, and Mes­sage­Con­sumer rather than their Queue-spe­cific coun­ter­parts.



                                                                                                    示例: .NET 消息队列

                                                                                                    Ex­ample: .NET Mes­sageQueue

                                                                                                    在.NET中,MessageQueue类实现了点对点通道[ SysMsg]。实现.NET消息传递的MSMQ在3.0版本之前仅支持点对点消息传递,因此.NET支持点对点消息传递。JMS 将连接工厂、连接、会话、发送者和队列的职责分开,而 MessageQueue 则完成这一切。

                                                                                                    In .NET, the Mes­sageQueue class im­ple­ments a point-to-point chan­nel [SysMsg]. MSMQ, which im­ple­ments .NET mes­saging, sup­por­ted only point-to-point mes­saging prior to ver­sion 3.0, so point-to-point is what .NET sup­ports. Whereas JMS sep­ar­ates the re­spons­ib­il­it­ies of the con­nec­tion fact­ory, con­nec­tion, ses­sion, sender, and queue, a Mes­sageQueue does it all.

                                                                                                    在MessageQueue上发送消息,如下所示:

                                                                                                    Send a mes­sage on a Mes­sageQueue like this:

                                                                                                    MessageQueue 队列 = new MessageQueue("MyQ​​ueue");
                                                                                                    queue.Send("消息的内容。");
                                                                                                    
                                                                                                    Mes­sageQueue queue = new Mes­sageQueue("MyQueue");
                                                                                                    queue.Send("The con­tents of the mes­sage.");
                                                                                                    

                                                                                                    在MessageQueue上接收消息,如下所示:

                                                                                                    Re­ceive a mes­sage on a Mes­sageQueue like this:

                                                                                                    MessageQueue 队列 = new MessageQueue("MyQ​​ueue");
                                                                                                    消息消息=queue.Receive();
                                                                                                    字符串内容 = (String) message.Body();
                                                                                                    
                                                                                                    Mes­sageQueue queue = new Mes­sageQueue("MyQueue");
                                                                                                    Mes­sage mes­sage = queue.Re­ceive();
                                                                                                    String con­tents = (String) mes­sage.Body();
                                                                                                    



                                                                                                      发布订阅通道

                                                                                                      Publish-Subscribe Channel

                                                                                                      图形/publishsubscribe_icon.gif

                                                                                                      应用程序正在使用消息传递来宣布事件。

                                                                                                      An ap­plic­a­tion is using Mes­saging to an­nounce events.

                                                                                                      发送者如何向所有感兴趣的接收者广播事件?

                                                                                                      How can the sender broad­cast an event to all in­ter­ested re­ceiv­ers?



                                                                                                      幸运的是,有一些完善的实施广播的模式。观察者模式[ GoF]描述了将观察者与其主体(即事件的发起者)解耦的需要,以便主体可以轻松地向所有感兴趣的观察者提供事件通知,无论有多少观察者(甚至没有)。发布者-订阅者模式[ POSA ]通过添加用于通信事件通知的事件通道的概念来扩展观察者。

                                                                                                      Luck­ily, there are well-es­tab­lished pat­terns for im­ple­ment­ing broad­cast­ing. The Ob­server pat­tern [GoF] de­scribes the need to de­couple ob­serv­ers from their sub­ject (that is, the ori­gin­ator of the event) so that the sub­ject can easily provide event no­ti­fic­a­tion to all in­ter­ested ob­serv­ers no matter how many ob­serv­ers there are (even none). The Pub­lisher-Sub­scriber pat­tern [POSA] ex­pands upon Ob­server by adding the notion of an event chan­nel for com­mu­nic­at­ing event no­ti­fic­a­tions.

                                                                                                      这就是理论,但它如何与消息传递一起工作呢?事件可以打包为消息,以便消息传递能够可靠地将事件传达给观察者(订阅者)。那么,事件通道就是Message Channel 。但是消息传递通道如何将事件正确地传达给所有订阅者呢?

                                                                                                      That's the theory, but how does it work with mes­saging? The event can be pack­aged as a Mes­sage so that mes­saging will re­li­ably com­mu­nic­ate the event to the ob­serv­ers (sub­scribers). Then, the event chan­nel is a Mes­sage Chan­nel. But how will a mes­saging chan­nel prop­erly com­mu­nic­ate the event to all of the sub­scribers?

                                                                                                      每个订阅者需要被通知一次特定事件,但不应该被重复通知同一事件。在通知所有订阅者之前,该事件不能被视为已消耗,但一旦通知了,该事件就可以被视为已消耗,并且应该从通道中消失。然而,让订阅者协调以确定何时使用消息违反了观察者模式的解耦。并发消费者不应该竞争,但应该能够共享事件消息。

                                                                                                      Each sub­scriber needs to be no­ti­fied of a par­tic­u­lar event once but should not be no­ti­fied re­peatedly of the same event. The event cannot be con­sidered con­sumed until all of the sub­scribers have been no­ti­fied, but once they have, the event can be con­sidered con­sumed and should dis­ap­pear from the chan­nel. Yet, having the sub­scribers co­ordin­ate to de­term­ine when a mes­sage is con­sumed vi­ol­ates the de­coup­ling of the Ob­server pat­tern. Con­cur­rent con­sumers should not com­pete but should be able to share the event mes­sage.

                                                                                                      在发布-订阅通道上发送事件该通道将特定事件的副本传递给每个接收者。

                                                                                                      Send the event on a Pub­lish-Sub­scribe Chan­nel, which de­liv­ers a copy of a par­tic­u­lar event to each re­ceiver.

                                                                                                      图形/04inf02.gif



                                                                                                      发布-订阅通道的工作原理如下:它有一个输入通道,可分为多个输出通道,每个通道对应一个订阅者。当事件发布到通道中时,发布-订阅通道会将消息的副本传递到每个输出通道。通道的每个输出端只有一个订阅者,一条消息只能消费一次。通过这种方式,每个订阅者只收到一次消息,并且消耗的副本从他们的频道中消失。

                                                                                                      A Pub­lish-Sub­scribe Chan­nel works like this: It has one input chan­nel that splits into mul­tiple output chan­nels, one for each sub­scriber. When an event is pub­lished into the chan­nel, the Pub­lish-Sub­scribe Chan­nel de­liv­ers a copy of the mes­sage to each of the output chan­nels. Each output end of the chan­nel has only one sub­scriber, which is al­lowed to con­sume a mes­sage only once. In this way, each sub­scriber gets the mes­sage only once, and con­sumed copies dis­ap­pear from their chan­nels.

                                                                                                      发布-订阅通道可以是一个有用的调试工具。即使消息注定只能发送给单个接收者,但使用发布-订阅通道可以让您在不干扰现有消息流的情况下窃听消息通道。在调试消息传递应用程序时,监视通道上的所有流量非常有帮助。它还可以使您免于将大量打印语句插入到参与消息传递解决方案的每个应用程序中。创建一个程序来侦听所有活动通道上的消息并将其记录到文件中,可以提供与消息存储相同的许多好处。

                                                                                                      A Pub­lish-Sub­scribe Chan­nel can be a useful de­bug­ging tool. Even though a mes­sage is destined to only a single re­ceiver, using a Pub­lish-Sub­scribe Chan­nel allows you to eaves­drop on a mes­sage chan­nel without dis­turb­ing the ex­ist­ing mes­sage flow. Mon­it­or­ing all traffic on a chan­nel can be tre­mend­ously help­ful when de­bug­ging mes­saging ap­plic­a­tions. It can also save you from in­sert­ing a ton of print state­ments into each ap­plic­a­tion that par­ti­cip­ates in the mes­saging solu­tion. Cre­at­ing a pro­gram that listens for mes­sages on all active chan­nels and logs them to a file can provide many of the same be­ne­fits that a Mes­sage Store brings.

                                                                                                      然而,窃听发布-订阅通道的能力也可能成为一个缺点。如果您的消息传递解决方案在工资系统和会计系统之间传输工资数据,您可能不希望允许任何人编写简单的程序来侦听消息流量。点对点通道在一定程度上缓解了这个问题:因为窃听者会消耗通道外的消息,并且消息会突然丢失,所以可以很快检测到这种情况。然而,许多消息队列实现提供了查看功能,使消费者可以查看队列内的消息而不消耗任何消息。因此,订阅消息频道是应该受到安全策略限制的操作。许多(但不是全部)商业消息传递实现都实施了此类限制。此外,创建一个监控工具来记录消息通道的活动订阅者可能是一个有用的系统管理工具。

                                                                                                      How­ever, the abil­ity to eaves­drop on a Pub­lish-Sub­scribe Chan­nel can also turn into a dis­ad­vant­age. If your mes­saging solu­tion trans­mits payroll data between the payroll system and the ac­count­ing system, you may not want to allow anyone to write a simple pro­gram to listen to the mes­sage traffic. Point-to-Point Chan­nels al­le­vi­ate the prob­lem some­what: Be­cause the eaves­drop­per would con­sume mes­sages off the chan­nel and mes­sages would sud­denly be miss­ing, the situ­ation could be de­tec­ted very quickly. How­ever, many mes­sage queue im­ple­ment­a­tions provide peek func­tions that let con­sumers look at mes­sages inside a queue without con­sum­ing any of the mes­sages. As a result, sub­scrib­ing to a Mes­sage Chan­nel is an op­er­a­tion that should be re­stric­ted by se­cur­ity policies. Many (but not all) com­mer­cial mes­saging im­ple­ment­a­tions im­ple­ment such re­stric­tions. In ad­di­tion, cre­at­ing a mon­it­or­ing tool that logs active sub­scribers to Mes­sage Chan­nels can be a useful sys­tems man­age­ment tool.

                                                                                                      通配符订阅者

                                                                                                      Wildcard Subscribers

                                                                                                      许多消息系统允许发布-订阅通道的订阅者指定特殊的通配符。这是一项强大的技术,允许订阅者同时订阅多个频道。例如,如果应用程序将消息发布到通道MyCorp/Prod/OrderProcessing/NewOrdersMyCorp/Prod/OrderProcessing/CancelledOrders ,则应用程序可以订阅 MyCorp /Prod/OrderProcessing/* 并接收与订单处理相关的所有消息。另一个应用程序可以订阅MyCorp/Dev/**接收开发环境中所有应用程序发送的所有消息。仅允许订阅者使用通配符;发布者始终需要将消息发布到特定渠道。通配符订阅者的具体功能和语法因不同消息传递供应商而异。

                                                                                                      Many mes­saging sys­tems allow sub­scribers to Pub­lish-Sub­scribe Chan­nels to spe­cify spe­cial wild­card char­ac­ters. This is a power­ful tech­nique to allow sub­scribers to sub­scribe to mul­tiple chan­nels at once. For ex­ample, if an ap­plic­a­tion pub­lishes mes­sages to the chan­nels MyCorp/Prod/Or­der­Pro­cess­ing/Ne­wOrders and MyCorp/Prod/Or­der­Pro­cess­ing/Can­celle­dOrders, an ap­plic­a­tion could sub­scribe to MyCorp/Prod/Or­der­Pro­cess­ing/* and re­ceive all mes­sages re­lated to order pro­cess­ing. An­other ap­plic­a­tion could sub­scribe to MyCorp/Dev/** to re­ceive all mes­sages sent by all ap­plic­a­tions in the de­vel­op­ment en­vir­on­ment. Only sub­scribers are al­lowed to use wild­cards; pub­lish­ers are always re­quired to pub­lish a mes­sage to a spe­cific chan­nel. The spe­cific cap­ab­il­it­ies and syntax for wild­card sub­scribers vary between the dif­fer­ent mes­saging vendors.



                                                                                                      事件消息通常在发布,因为多个依赖者通常对一个事件感兴趣。订阅者可以是持久订阅者,也可以是非持久订阅者,请参阅第 10章“消息传送端点”中的持久订阅者。如果订阅者应确认通知,请使用Request-Reply,其中通知是请求,确认是回复。将每条消息存储在发布-订阅通道上直到所有订阅者都使用该消息可能需要大量的消息存储。为了帮助缓解此问题,发送到发布-订阅通道的消息可以使用消息过期

                                                                                                      An Event Mes­sage is usu­ally sent on a Pub­lish-Sub­scribe Chan­nel be­cause mul­tiple de­pend­ents are often in­ter­ested in an event. A sub­scriber can be dur­able or non­dur­able­see Dur­able Sub­scriber in the Chapter 10, "Mes­saging En­d­points." If no­ti­fic­a­tions should be ac­know­ledged by the sub­scribers, use Re­quest-Reply, where the no­ti­fic­a­tion is the re­quest and the ac­know­ledg­ment is the reply. Stor­ing each mes­sage on a Pub­lish-Sub­scribe Chan­nel until all sub­scribers con­sume the mes­sage can re­quire a large amount of mes­sage stor­age. To help al­le­vi­ate this issue, mes­sages sent to a Pub­lish-Sub­scribe Chan­nel can use Mes­sage Ex­pir­a­tion.

                                                                                                      示例: 股票交易

                                                                                                      Ex­ample: Stock Trad­ing

                                                                                                      在股票交易系统中,许多系统可能需要收到交易完成的通知,因此将它们全部设为发布交易完成的发布-订阅通道的订阅者。

                                                                                                      In a stock trad­ing system, many sys­tems may need to be no­ti­fied of the com­ple­tion of a trade, so make them all sub­scribers of a Pub­lish-Sub­scribe Chan­nel that pub­lishes trade com­ple­tions.

                                                                                                      同样,许多消费者对显示或处理当前股票报价数据感兴趣。因此,股票报价应该通过发布-订阅通道进行广播

                                                                                                      Like­wise, many con­sumers are in­ter­ested in dis­play­ing or pro­cess­ing cur­rent stock quote data. There­fore, stock quotes should be broad­cast across a Pub­lish-Sub­scribe Chan­nel.



                                                                                                      示例: JMS 主题

                                                                                                      Ex­ample: JMS Topic

                                                                                                      在 JMS 中,发布-订阅通道实现主题接口。发送者使用TopicPublisher发送消息;每个接收者使用自己的TopicSubscriber 来接收消息 [JMS 1.1],[ Hapner ]

                                                                                                      In JMS, a Pub­lish-Sub­scribe Chan­nel im­ple­ments the Topic in­ter­face. The sender uses a Top­icPub­lisher to send mes­sages; each re­ceiver uses its own Top­ic­Sub­scriber to re­ceive mes­sages [JMS 1.1], [Hapner].

                                                                                                      应用程序使用 TopicPublisher发送如下消息:

                                                                                                      An ap­plic­a­tion uses a Top­icPub­lisher to send a mes­sage like this:

                                                                                                      
                                                                                                      topic topic = // 通过JNDI获取主题
                                                                                                      TopicConnectionFactoryfactory = // 获取连接工厂
                                                                                                      图形/ccc.gif通过 JNDI
                                                                                                      TopicConnection连接=factory.createTopicConnection();
                                                                                                      TopicSession 会话 = connection.createTopicSession(true,
                                                                                                      图形/ccc.gif会话.AUTO_ACKNOWLEDGE);
                                                                                                      TopicPublisher 发布者 = session.createPublisher(topic);
                                                                                                      
                                                                                                      Message message = session.createTextMessage("消息内容
                                                                                                      图形/ccc.gif信息。”);
                                                                                                      
                                                                                                      发布者.publish(消息);
                                                                                                      
                                                                                                      
                                                                                                      Topic topic = // obtain the topic via JNDI
                                                                                                      Top­ic­Con­nec­tion­Fact­ory fact­ory = // obtain the con­nec­tion fact­ory
                                                                                                       via JNDI
                                                                                                      Top­ic­Con­nec­tion con­nec­tion = fact­ory.cre­at­eTop­ic­Con­nec­tion();
                                                                                                      Top­ic­Ses­sion ses­sion = con­nec­tion.cre­at­eTop­ic­Ses­sion(true,
                                                                                                       Ses­sion.AUTO_AC­KNOW­LEDGE);
                                                                                                      Top­icPub­lisher pub­lisher = ses­sion.cre­ate­Pub­lisher(topic);
                                                                                                      
                                                                                                      Mes­sage mes­sage = ses­sion.cre­at­e­Text­Mes­sage("The con­tents of the
                                                                                                       mes­sage.");
                                                                                                      
                                                                                                      pub­lisher.pub­lish(mes­sage);
                                                                                                      

                                                                                                      应用程序使用TopicSubscriber 来接收如下消息:

                                                                                                      An ap­plic­a­tion uses a Top­ic­Sub­scriber to re­ceive a mes­sage like this:

                                                                                                      
                                                                                                      topic topic = // 通过JNDI获取主题
                                                                                                      TopicConnectionFactoryfactory = // 获取连接工厂
                                                                                                      图形/ccc.gif通过 JNDI
                                                                                                      TopicConnection连接=factory.createTopicConnection();
                                                                                                      TopicSession 会话 = connection.createTopicSession(true,
                                                                                                      图形/ccc.gif会话.AUTO_ACKNOWLEDGE);
                                                                                                      TopicSubscriber 订阅者 = session.createSubscriber(topic);
                                                                                                      
                                                                                                      TextMessage 消息 = (TextMessage) 订阅者.receive();
                                                                                                      字符串内容 = message.getText();
                                                                                                      
                                                                                                      
                                                                                                      Topic topic = // obtain the topic via JNDI
                                                                                                      Top­ic­Con­nec­tion­Fact­ory fact­ory = // obtain the con­nec­tion fact­ory
                                                                                                       via JNDI
                                                                                                      Top­ic­Con­nec­tion con­nec­tion = fact­ory.cre­at­eTop­ic­Con­nec­tion();
                                                                                                      Top­ic­Ses­sion ses­sion = con­nec­tion.cre­at­eTop­ic­Ses­sion(true,
                                                                                                       Ses­sion.AUTO_AC­KNOW­LEDGE);
                                                                                                      Top­ic­Sub­scriber sub­scriber = ses­sion.cre­ate­Sub­scriber(topic);
                                                                                                      
                                                                                                      Text­Mes­sage mes­sage = (Text­Mes­sage) sub­scriber.re­ceive();
                                                                                                      String con­tents = mes­sage.get­Text();
                                                                                                      

                                                                                                      注意:JMS 1.1 统一了点对点和发布-订阅域的客户端 API,因此此处显示的代码可以简化为使用Destination 、 ConnectionFactory 、 Connection 、 SessionMessageProducerMessageConsumer 而不是特定于主题的对应项

                                                                                                      NOTE: JMS 1.1 uni­fies the client APIs for the point-to-point and pub­lish-sub­scribe do­mains, so the code shown here can be sim­pli­fied to use Des­tin­a­tion, Con­nec­tion­Fact­ory, Con­nec­tion, Ses­sion, Mes­sage­Pro­du­cer, and Mes­sage­Con­sumer rather than their Topic-spe­cific coun­ter­parts.



                                                                                                      示例: MSMQ 一对多消息传递

                                                                                                      Ex­ample: MSMQ One-to-Many Mes­saging

                                                                                                      MSMQ 3.0 [ MSMQ ]中的一个新功能是一对多消息传递模型,它有两种不同的方法。

                                                                                                      A new fea­ture in MSMQ 3.0 [MSMQ] is a one-to-many mes­saging model, which has two dif­fer­ent ap­proaches.

                                                                                                      1. 实时消息多播 这种 方法与发布-订阅最接近,但其实现完全依赖于通过实用通用多播 (PGM) 协议进行的 IP 多播,因此该功能不能与不基于 IP 的其他协议一起使用。

                                                                                                      2. Real-Time Mes­saging Mul­tic­ast This ap­proach most closely matches pub­lish-sub­scribe, but its im­ple­ment­a­tion is en­tirely de­pend­ent on IP mul­tic­ast­ing via the Prag­matic Gen­eral Mul­tic­ast (PGM) pro­tocol, so this fea­ture cannot be used with other pro­to­cols that are not based on IP.

                                                                                                      3. 分发列表和多元素格式名称分发列表使发送方能够显式地将消息发送到接收方列表(但这违反了观察者模式 的精神,因为发送方现在必须了解接收方)。因此,此功能更类似于“收件人列表”而不是“发布-订阅通道” 。多元素格式名称是一种符号通道说明符,它动态映射到多个真实通道,这更像是发布-订阅通道的精神,但仍然迫使发送者在真实通道和符号通道之间进行选择。

                                                                                                      4. Dis­tri­bu­tion Lists and Mul­tiple-Ele­ment Format Names A Dis­tri­bu­tion List en­ables the sender to ex­pli­citly send a mes­sage to a list of re­ceiv­ers (but this vi­ol­ates the spirit of the Ob­server pat­tern be­cause the sender now has to be aware of the re­ceiv­ers). There­fore, this fea­ture more closely re­sembles a Re­cip­i­ent List than a Pub­lish-Sub­scribe Chan­nel. A Mul­tiple-Ele­ment Format Name is a sym­bolic chan­nel spe­cifier that dy­nam­ic­ally maps to mul­tiple real chan­nels, which is more the spirit of a Pub­lish-Sub­scribe Chan­nel but still forces the sender to choose between a real chan­nel and a sym­bolic one.

                                                                                                      .NET 公共语言运行时 (CLR) 不提供对使用一对多消息传递模型的直接支持。但是,可以通过 COM 接口 [ MDMSG ]访问此功能,该接口可以嵌入到 .NET 代码中。

                                                                                                      The .NET Common Lan­guage Runtime (CLR) does not provide direct sup­port for using the one-to-many mes­saging model. How­ever, this func­tion­al­ity can be ac­cessed through the COM in­ter­face [MDMSG], which can be em­bed­ded in .NET code.



                                                                                                      示例: 简单消息传递

                                                                                                      Ex­ample: Simple Mes­saging

                                                                                                      第 6 章“插曲:简单消息传递”中的 JMS 发布-订阅示例显示了如何使用消息传递跨多个进程实现 Observer的示例。

                                                                                                      The JMS Pub­lish-Sub­scribe ex­ample in Chapter 6, "In­ter­lude: Simple Mes­saging," shows an ex­ample of how to im­ple­ment Ob­server across mul­tiple pro­cesses using mes­saging.



                                                                                                        数据类型通道

                                                                                                        Datatype Channel

                                                                                                        图形/datatypechannel_icon.gif

                                                                                                        应用程序使用消息传递来传输不同类型的数据,例如不同类型的文档。

                                                                                                        An ap­plic­a­tion is using Mes­saging to trans­fer dif­fer­ent types of data, such as dif­fer­ent types of doc­u­ments.

                                                                                                        应用程序如何发送数据项以便接收者知道如何处理它?

                                                                                                        How can the ap­plic­a­tion send a data item such that the re­ceiver will know how to pro­cess it?



                                                                                                        所有消息都只是消息系统定义的相同消息类型的实例,并且任何消息的内容最终都只是一个字节数组。虽然这种简单的结构(字节束)对于消息传递系统能够传输消息而言足够具体,但对于接收者能够处理消息内容而言,它还不够具体。

                                                                                                        All mes­sages are just in­stances of the same mes­sage type, as defined by the mes­saging system, and the con­tents of any mes­sage are ul­ti­mately just a byte array. While this simple struc­turea bundle of bytesis spe­cific enough for a mes­saging system to be able to trans­mit a mes­sage, it is not spe­cific enough for a re­ceiver to be able to pro­cess a mes­sage's con­tents.

                                                                                                        接收者必须知道消息内容的数据结构和数据格式。结构可以是字符数组、字节数组、序列化对象、XML文档等。格式可以是字节或字符的记录结构、序列化对象的类、XML 文档的模式定义等。所有这些知识都被宽松地称为消息类型,指的是消息内容的结构和格式。

                                                                                                        A re­ceiver must know the mes­sage con­tent's data struc­ture and data format. The struc­ture could be char­ac­ter array, byte array, seri­al­ized object, XML doc­u­ment, and so on. The format could be the record struc­ture of the bytes or char­ac­ters, the class of the seri­al­ized object, the schema defin­i­tion of the XML doc­u­ment, and so on. All of this know­ledge is loosely re­ferred to as the mes­sage's type, re­fer­ring to both the struc­ture and format of the mes­sage's con­tents.

                                                                                                        接收者必须知道它正在接收什么类型的消息,否则它不知道如何处理它们。例如,发送者可能希望发送不同的对象,例如采购订单、报价和查询。然而,接收器可能会采取不同的步骤来处理每一个,因此它必须知道哪个是哪个。如果发送方只是通过单个消息通道将所有这些发送到接收方,则接收方将不知道如何处理每一项。

                                                                                                        The re­ceiver must know what type of mes­sages it's re­ceiv­ing, or it won't know how to pro­cess them. For ex­ample, a sender may wish to send dif­fer­ent ob­jects such as pur­chase orders, price quotes, and quer­ies. Yet, a re­ceiver will prob­ably take dif­fer­ent steps to pro­cess each of those, so it has to know which is which. If the sender simply sends all of these to the re­ceiver via a single mes­sage chan­nel, the re­ceiver will not know how to pro­cess each one.

                                                                                                        混合数据类型

                                                                                                        Mixed Data Types

                                                                                                        图形/04inf03.gif

                                                                                                        发送者知道它正在发送什么消息类型,那么如何将其传达给接收者呢?发送者可以在消息的标头中放置一个标志(请参阅格式指示符),但接收者将需要一个 case 语句。发送方可以将数据包装在命令消息中,对于每种类型的数据使用不同的命令,但是当消息试图做的只是将数据传输到接收方时,这会告诉接收方如何处理数据。

                                                                                                        The sender knows what mes­sage type it's send­ing, so how can this be com­mu­nic­ated to the re­ceiver? The sender could put a flag in the mes­sage's header (see Format In­dic­ator), but then the re­ceiver will need a case state­ment. The sender could wrap the data in a Com­mand Mes­sage with a dif­fer­ent com­mand for each type of data, but that pre­sumes to tell the re­ceiver what to do with the data when all that the mes­sage is trying to do is trans­mit the data to the re­ceiver.

                                                                                                        处理项目集合时,会出现与消息传递不同的类似问题。该集合必须是同类的,这意味着所有项目都是相同的类型,以便处理器知道项目的类型是什么以及如何操作它。许多集合实现并不强制所有项目都属于特定类型,因此程序员必须设计代码以确保集合中的所有项目都属于同一类型。否则,可以将不同类型的项目添加到集合中,但处理这些项目的代码将不知道如何操作每个项目,因为它不知道特定项目实现的类型。

                                                                                                        A sim­ilar prob­lem­one sep­ar­ate from mes­sagin­goc­curs when pro­cess­ing a col­lec­tion of items. The col­lec­tion must be ho­mo­gen­eous, mean­ing that all of the items are the same type, so that the pro­cessor knows what an item's type is and thus how to ma­nip­u­late it. Many col­lec­tion im­ple­ment­a­tions do not force all of the items to be of a spe­cific type, so pro­gram­mers must design their code to ensure that all of the items in a col­lec­tion are of the same type. Oth­er­wise, items of dif­fer­ent types can be added to a col­lec­tion, but the code pro­cess­ing those items will not know how to ma­nip­u­late each one be­cause it won't know what type a par­tic­u­lar item im­ple­ments.

                                                                                                        同样的原则也适用于消息传递,因为通道上的消息同样必须属于同一类型。最简单的解决方案是所有消息都采用相同的格式。如果格式必须不同,则它们都必须有可靠的格式指示符。虽然通道并不强制所有消息都属于同一类型,但接收者需要它们是同一类型,以便知道如何处理它们。

                                                                                                        The same prin­ciple ap­plies to mes­saging be­cause the mes­sages on a chan­nel must like­wise all be of the same type. The simplest solu­tion is for all of the mes­sages to be of the same format. If the formats must differ, they must all have a re­li­able format in­dic­ator. While the chan­nel does not force all mes­sages to be of the same type, the re­ceiver needs them to be so that it knows how to pro­cess them.

                                                                                                        为每个数据类型使用单独的数据类型通道,以便特定通道上的所有数据都具有相同的类型。

                                                                                                        Use a sep­ar­ate Data­type Chan­nel for each data­type so that all data on a par­tic­u­lar chan­nel is of the same type.

                                                                                                        图形/04inf04.gif



                                                                                                        通过对每种数据类型使用单独的数据类型通道,给定通道上的所有消息将包含相同类型的数据。发送者知道数据的类型,需要选择适当的通道来发送数据。接收者知道数据是通过哪个通道接收的,也就知道它是什么类型。

                                                                                                        By using a sep­ar­ate Data­type Chan­nel for each type of data, all of the mes­sages on a given chan­nel will con­tain the same type of data. The sender, know­ing what type the data is, will need to select the ap­pro­pri­ate chan­nel to send it on. The re­ceiver, know­ing what chan­nel the data was re­ceived on, will know what type it is.

                                                                                                        如图所示,由于发送方要发送三种不同类型的数据(采购订单、报价和查询),因此应该使用三种不同的通道。发送项目时,发送者必须为该项目选择适当的数据类型通道。当接收一个项目时,接收者根据接收该项目的数据类型通道知道该项目的类型。

                                                                                                        As shown in the fig­ures, since the sender wants to send three dif­fer­ent types of data (pur­chase orders, price quotes, and quer­ies), it should use three dif­fer­ent chan­nels. When send­ing an item, the sender must select the ap­pro­pri­ate Data­type Chan­nel for that item. When re­ceiv­ing an item, the re­ceiver knows the item's type be­cause of the data­type chan­nel on which it re­ceived the item.

                                                                                                        服务质量渠道

                                                                                                        Quality-of-Service Channel

                                                                                                        一个相关的策略是服务质量渠道。有时,企业希望传输具有与另一组消息不同的服务级别的一组消息。例如,传入的新订单消息可能是企业收入的主要来源,并且应该通过非常可靠的渠道进行传输(例如,使用保证交付))尽管存在潜在的性能开销。另一方面,丢失代表订单状态请求的消息并不是世界末日,因此我们可能倾向于使用更快但不太可靠的通道。有时这可以在单个通道上完成,例如通过使用消息优先级。然而,通常最好在创建通道时定义服务质量参数,而不是将决定权留给发送消息的应用程序代码。因此,最好在其自己的信道上传输每组消息,以便可以根据其消息组的需要来调整每个信道的服务质量。

                                                                                                        A re­lated strategy is a Qual­ity-of-Ser­vice Chan­nel. Some­times, an en­ter­prise wants to trans­mit one group of mes­sages with a dif­fer­ent level of ser­vice from an­other group of mes­sages. For ex­ample, in­com­ing New Order mes­sages may be the primary source of rev­enue for a busi­ness and should be trans­por­ted over a very re­li­able chan­nel (e.g., using Guar­an­teed De­liv­ery) des­pite the po­ten­tial per­form­ance over­head. On the other hand, losing a mes­sage that rep­res­ents a re­quest for order status is not the end of the world, so we may be in­clined to use a faster but less re­li­able chan­nel. This can some­times be ac­com­plished on a single chan­nel, for ex­ample, by using mes­sage pri­or­it­ies. How­ever, gen­er­ally it is a better idea to define qual­ity-of-ser­vice para­met­ers when cre­at­ing the chan­nel rather than leav­ing the de­cision up to the ap­plic­a­tion code that sends the mes­sages. There­fore, it is best to trans­mit each group of mes­sages on its own chan­nel so that each chan­nel's qual­ity-of-ser­vice can be tuned for the needs of its group of mes­sages.



                                                                                                        正如消息通道中所讨论的,通道很便宜,但它们不是免费的。应用程序可能需要传输许多不同的数据类型,太多以至于无法为每个数据类型创建单独的数据类型通道。在这种情况下,多个数据类型可以通过为每种类型使用不同的选择性消费者来共享单个通道。这使得单个物理通道就像多个逻辑Datatype Channel一样(一种称为多路复用的) 。数据类型通道解释了为什么通道上的所有消息必须具有相同的格式,规范数据模型解释了企业中所有渠道上的所有消息应如何遵循统一的数据模型。

                                                                                                        As dis­cussed under Mes­sage Chan­nel, chan­nels are cheap, but they are not free. An ap­plic­a­tion may need to trans­mit many dif­fer­ent data­types, too many to create a sep­ar­ate Data­type Chan­nel for each. In this case, mul­tiple data­types can share a single chan­nel by using a dif­fer­ent Se­lect­ive Con­sumer for each type. This makes a single phys­ical chan­nel act like mul­tiple lo­gical Data­type Chan­nels (a strategy called mul­ti­plex­ing). Whereas Data­type Chan­nel ex­plains why all mes­sages on a chan­nel must be of the same format, Ca­non­ical Data Model ex­plains how all mes­sages on all chan­nels in an en­ter­prise should follow a uni­fied data model.

                                                                                                        如果我们想使用Datatype Channel但现有的消息发布者只是将所有消息发送到单个通道,我们可以使用基于内容路由器来解复用消息路由器将消息流划分为多个Datatype Channel ,每个 Datatype Channel 仅承载一种类型的消息。

                                                                                                        If we would like to use Data­type Chan­nels but an ex­ist­ing mes­sage pub­lisher simply sends all mes­sages to a single chan­nel, we can use a Con­tent-Based Router to de­mul­ti­plex the mes­sages. The router di­vides the mes­sage stream across mul­tiple Data­type Chan­nels, each of which car­ries mes­sages of only one type.

                                                                                                        消息调度程序除了提供并发消息消费之外,还可以用于以特定于类型的方式处理一组通用消息。每个消息必须指定其类型(通常通过消息头中的格式指示符);调度程序检测消息的类型并将其调度到特定于类型的执行者进行处理。通道上的消息仍然都是同一类型,但该类型是调度程序支持的更通用的类型,而不是各个执行者需要的更具体的类型。

                                                                                                        A Mes­sage Dis­patcher, be­sides provid­ing con­cur­rent mes­sage con­sump­tion, can be used to pro­cess a gen­eric set of mes­sages in type-spe­cific ways. Each mes­sage must spe­cify its type (typ­ic­ally by a format in­dic­ator in the mes­sage's header); the dis­patcher de­tects the mes­sage's type and dis­patches it to a type-spe­cific per­former for pro­cess­ing. The mes­sages on the chan­nel are still all of the same type, but that type is the more gen­eral one that the dis­patcher sup­ports, not the more spe­cific ones that the vari­ous per­formers re­quire.

                                                                                                        格式指示符用于区分同一数据的不同格式版本,从而使这些不同的格式能够在同一数据类型通道上发送

                                                                                                        A Format In­dic­ator is used to dis­tin­guish dif­fer­ent format ver­sions of the same data, which in turn en­ables these dif­fer­ent formats to be sent on the same Data­type Chan­nel.

                                                                                                        示例: 股票交易

                                                                                                        Ex­ample: Stock Trad­ing

                                                                                                        在股票交易系统中,如果报价请求的格式与交易请求的格式不同,则系统应使用单独的数据类型通道来通信每种请求。同样,地址变更公告可能与投资组合经理变更公告具有不同的格式,因此每种公告都应该有自己的Datatype Channel

                                                                                                        In a stock trad­ing system, if the format of a quote re­quest is dif­fer­ent from that of a trade re­quest, the system should use a sep­ar­ate Data­type Chan­nel for com­mu­nic­at­ing each kind of re­quest. Like­wise, a change-of-ad­dress an­nounce­ment may have a dif­fer­ent format from a change-of-port­fo­lio-man­ager an­nounce­ment, so each kind of an­nounce­ment should have its own Data­type Chan­nel.



                                                                                                          消息通道无效

                                                                                                          Invalid Message Channel

                                                                                                          图形/invalidmessage_icon.gif

                                                                                                          应用程序正在使用消息传递来接收消息

                                                                                                          An ap­plic­a­tion is using Mes­saging to re­ceive Mes­sages.

                                                                                                          消息接收者如何优雅地处理接收到的毫无意义的消息?

                                                                                                          How can a mes­saging re­ceiver grace­fully handle re­ceiv­ing a mes­sage that makes no sense?



                                                                                                          理论上,消息通道上的所有内容都只是消息,消息接收者只是处理消息。然而,要处理消息,接收者必须能够解释其数据并理解其含义。这并不总是可能的:消息正文可能会导致解析错误、词汇错误或验证错误。消息标头可能缺少所需的属性,或者属性值可能没有意义。发送者可能会将完美的消息放到错误的通道上,从而将其传输给错误的接收者。恶意发送者可能故意发送不正确的消息,只是为了扰乱接收者。接收者可能无法处理它收到的所有消息,因此它必须有其他方式来处理它认为无效的消息。

                                                                                                          In theory, everything on a Mes­sage Chan­nel is just a mes­sage, and mes­sage re­ceiv­ers just pro­cess mes­sages. How­ever, to pro­cess a mes­sage, a re­ceiver must be able to in­ter­pret its data and un­der­stand its mean­ing. This is not always pos­sible: The mes­sage body may cause pars­ing errors, lex­ical errors, or val­id­a­tion errors. The mes­sage header may be miss­ing needed prop­er­ties, or the prop­erty values may not make sense. A sender might put a per­fectly good mes­sage on the wrong chan­nel, trans­mit­ting it to the wrong re­ceiver. A ma­li­cious sender could pur­posely send an in­cor­rect mes­sage just to mess up the re­ceiver. A re­ceiver may not be able to pro­cess all the mes­sages it re­ceives, so it must have some other way to handle mes­sages it does not con­sider valid.

                                                                                                          消息通道应该是数据类型通道,其中通道上的每条消息都应该具有该通道的正确数据类型。如果发送方在通道上放置的消息的数据类型不正确,则消息传递系统将成功传输该消息,但接收方将无法识别该消息并且不知道如何处理该消息。

                                                                                                          A Mes­sage Chan­nel should be a Data­type Chan­nel, where each of the mes­sages on the chan­nel is sup­posed to be of the proper data­type for that chan­nel. If a sender puts a mes­sage on the chan­nel that is not the cor­rect data­type, the mes­saging system will trans­mit the mes­sage suc­cess­fully, but the re­ceiver will not re­cog­nize the mes­sage and will not know how to pro­cess it.

                                                                                                          数据类型或格式不正确的消息的一个示例是通道上本应包含文本消息的字节消息。另一个示例是格式不正确的消息,例如格式不正确或对于商定的 DTD 或模式无效的 XML 文档。就消息系统而言,这些消息没有任何问题,但接收者将无法处理它们,因此它们是无效的。

                                                                                                          One ex­ample of a mes­sage with an im­proper data­type or format is a byte mes­sage on a chan­nel that is sup­posed to con­tain text mes­sages. An­other ex­ample is a mes­sage whose format is not cor­rect, such as an XML doc­u­ment that is not well formed or that is not valid for the agreed-upon DTD or schema. There's noth­ing wrong with these mes­sages as far as the mes­saging system is con­cerned, but the re­ceiver will not be able to pro­cess them, so they are in­valid.

                                                                                                          不包含接收者期望的标头字段值的消息也是无效的。如果消息应该具有标头属性,例如相关标识符、消息序列标识符、返回地址等,但消息缺少这些属性,则消息传递系统将正确传递消息,但接收者不会能够成功处理它。

                                                                                                          Mes­sages that do not con­tain the header field values that the re­ceiver ex­pects are also in­valid. If a mes­sage is sup­posed to have header prop­er­ties such as a Cor­rel­a­tion Iden­ti­fier, Mes­sage Se­quence iden­ti­fi­ers, a Return Ad­dress, and so on, but the mes­sage is miss­ing the prop­er­ties, then the mes­saging system will de­liver the mes­sage prop­erly, but the re­ceiver will not be able to pro­cess it suc­cess­fully.

                                                                                                          无效消息

                                                                                                          In­valid Mes­sage

                                                                                                          图形/04inf05.gif

                                                                                                          当接收方发现它尝试处理的消息无效时,它应该如何处理该消息?它可以将消息放回到通道上,但随后该消息将被同一接收者或其他类似的接收者重新使用。同时,被忽略的无效消息会使通道变得混乱并损害性能。接收者可以使用无效消息并将其丢弃,但这往往会隐藏需要检测的消息传递问题。系统需要的是一种方法,将不正确的消息从通道中清除,并将它们放在不妨碍但可以检测到的位置,以诊断消息传递系统的问题。

                                                                                                          When the re­ceiver dis­cov­ers that the mes­sage it's trying to pro­cess is not valid, what should it do with the mes­sage? It could put the mes­sage back on the chan­nel, but then the mes­sage will just be re­con­sumed by the same re­ceiver or an­other like it. Mean­while, in­valid mes­sages that are being ig­nored will clut­ter the chan­nel and hurt per­form­ance. The re­ceiver could con­sume the in­valid mes­sage and throw it away, but that would tend to hide mes­saging prob­lems that need to be de­tec­ted. What the system needs is a way to clean im­proper mes­sages out of chan­nels and put them in a place where they will be out of the way but can be de­tec­ted to dia­gnose prob­lems with the mes­saging system.

                                                                                                          接收者应将不正确的消息移至无效消息通道,这是一个用于接收者无法处理的消息的特殊通道。

                                                                                                          The re­ceiver should move the im­proper mes­sage to an In­valid Mes­sage Chan­nel, a spe­cial chan­nel for mes­sages that could not be pro­cessed by their re­ceiv­ers.

                                                                                                          图形/04inf06.gif



                                                                                                          在设计供应用程序使用的消息系统时,管理员必须定义一个或多个Invalid Message Channel供应用程序使用。无效消息通道不会用于正常、成功的通信,因此如果它混杂着不正确的消息,那不会是问题。想要诊断不正确消息的错误处理程序可以使用无效通道上的接收器来检测可用的消息。

                                                                                                          When design­ing a mes­saging system for ap­plic­a­tions to use, the ad­min­is­trator must define one or more In­valid Mes­sage Chan­nels for the ap­plic­a­tions to use. The In­valid Mes­sage Chan­nel will not be used for normal, suc­cess­ful com­mu­nic­a­tion, so if it is cluttered with im­proper mes­sages, that will not be a prob­lem. An error hand­ler that wants to dia­gnose im­proper mes­sages can use a re­ceiver on the in­valid chan­nel to detect mes­sages as they become avail­able.

                                                                                                          无效消息通道就像消息传递的错误日志。当应用程序出现问题时,最好记录错误。当处理消息时出现问题时,最好将消息放在无效消息的通道上。如果浏览频道的任何人都不清楚为什么此消息无效,则应用程序还应该记录包含更多详细信息的错误。

                                                                                                          An In­valid Mes­sage Chan­nel is like an error log for mes­saging. When some­thing goes wrong in an ap­plic­a­tion, it's a good idea to log the error. When some­thing goes wrong pro­cess­ing a mes­sage, it's a good idea to put the mes­sage on the chan­nel for in­valid mes­sages. If it won't be ob­vi­ous to anyone brows­ing the chan­nel why this mes­sage is in­valid, the ap­plic­a­tion should also log an error with more de­tails.

                                                                                                          请记住,消息本质上既不是有效的也不是无效的,而是由接收者的上下文和期望做出此决定。一条消息对一个接收者可能有效,但对另一个接收者可能无效;两个这样的接收器不应共享同一频道。对通道上的一个接收者有效的消息也应该对该通道上的所有其他接收者有效。同样,如果一个接收者认为一条消息无效,那么所有其他接收者也应该如此。发送者有责任确保其在通道上发送的消息将被通道的接收者视为有效。否则,接收者将通过将发送者的消息重新路由到无效消息通道来忽略发送者的消息

                                                                                                          Keep in mind that a mes­sage is neither in­her­ently valid nor in­valid, but it is the re­ceiver's con­text and ex­pect­a­tions that make this de­term­in­a­tion. A mes­sage that may be valid for one re­ceiver may be in­valid for an­other re­ceiver; two such re­ceiv­ers should not share the same chan­nel. A mes­sage that is valid for one re­ceiver on a chan­nel should be valid for all other re­ceiv­ers on that chan­nel. Like­wise, if one re­ceiver con­siders a mes­sage in­valid, all other re­ceiv­ers should as well. It is the sender's re­spons­ib­il­ity to make sure that a mes­sage it sends on a chan­nel will be con­sidered valid by the chan­nel's re­ceiv­ers. Oth­er­wise, the re­ceiv­ers will ignore the sender's mes­sages by rerout­ing them to the In­valid Mes­sage Chan­nel.

                                                                                                          当消息结构正确但其内容在语义上不正确时,会出现类似但独立的问题。例如,命令消息可以指示接收者删除不存在的数据库记录。这不是消息传递错误,而是应用程序错误。因此,虽然将消息移至无效消息通道可能很诱人,但该消息没有任何问题,因此将其视为无效消息会产生误导。相反,像这样的错误应该作为无效的应用程序请求来处理,而不是无效的消息。

                                                                                                          A sim­ilar but sep­ar­ate prob­lem occurs when a mes­sage is struc­tured prop­erly, but its con­tents are se­mantic­ally in­cor­rect. For ex­ample, a Com­mand Mes­sage may in­struct the re­ceiver to delete a data­base record that does not exist. This is not a mes­saging error but an ap­plic­a­tion error. As such, while it may be tempt­ing to move the mes­sage to the In­valid Mes­sage Chan­nel, there is noth­ing wrong with the mes­sage, so treat­ing it as in­valid is mis­lead­ing. Rather, an error like this should be handled as an in­valid ap­plic­a­tion re­quest, not an in­valid mes­sage.

                                                                                                          当接收器被实现为服务激活器或消息传递网关时,消息处理错误和应用程序错误之间的区别变得更加简单和清晰。这些模式将消息处理代码与应用程序的其余部分分开。如果处理消息时发生错误,则该消息无效,应移至无效消息通道。如果在应用程序处理消息中的数据时发生这种情况,则这是一个与消息传递无关的应用程序错误。

                                                                                                          This dif­fer­ence between mes­sage-pro­cess­ing errors and ap­plic­a­tion errors be­comes sim­pler and clearer when the re­ceiver is im­ple­men­ted as a Ser­vice Ac­tiv­ator or Mes­saging Gate­way. These pat­terns sep­ar­ate mes­sage-pro­cess­ing code from the rest of the ap­plic­a­tion. If an error occurs while pro­cess­ing the mes­sage, the mes­sage is in­valid and should be moved to the In­valid Mes­sage Chan­nel. If it occurs while the ap­plic­a­tion pro­cesses the data from the mes­sage, that is an ap­plic­a­tion error that has noth­ing to do with mes­saging.

                                                                                                          内容被忽略的无效消息通道与被忽略的错误日志一样有用。无效消息通道上的消息表明应用程序集成存在问题,因此不应忽略这些消息;相反,应该对它们进行分析以确定出了什么问题,以便解决问题。理想情况下,这将是一个自动化过程,用于消耗无效消息、确定其原因并修复根本问题。然而,原因通常是编码或配置错误,需要开发人员或系统分析师进行评估和修复。至少,使用消息传递和Invalid Message Channel 的应用程序应该有一个进程来监视无效的消息通道,并在通道包含消息时向系统管理员发出警报。

                                                                                                          An In­valid Mes­sage Chan­nel whose con­tents are ig­nored is about as useful as an error log that is ig­nored. Mes­sages on the In­valid Mes­sage Chan­nel in­dic­ate ap­plic­a­tion in­teg­ra­tion prob­lems, so those mes­sages should not be ig­nored; rather, they should be ana­lyzed to de­term­ine what went wrong so that the prob­lem can be fixed. Ideally, this would be an auto­mated pro­cess that con­sumed in­valid mes­sages, de­term­ined their cause, and fixed the un­der­ly­ing prob­lems. How­ever, the cause is often a coding or con­fig­ur­a­tion error that re­quires a de­ve­loper or system ana­lyst to eval­u­ate and repair. At the very least, ap­plic­a­tions that use mes­saging and In­valid Mes­sage Chan­nels should have a pro­cess that mon­it­ors the In­valid Mes­sage Chan­nel and alerts system ad­min­is­trat­ors whenever the chan­nel con­tains mes­sages.

                                                                                                          许多消息传递系统实现的类似概念是死信通道。无效消息通道适用于可以传递和接收但未处理的消息,而死信通道适用于消息传递系统无法正确传递的消息。

                                                                                                          A sim­ilar concept im­ple­men­ted by many mes­saging sys­tems is a Dead Letter Chan­nel. Whereas an In­valid Mes­sage Chan­nel is for mes­sages that can be de­livered and re­ceived but not pro­cessed, a Dead Letter Chan­nel is for mes­sages that the mes­saging system cannot de­liver prop­erly.

                                                                                                          示例: 股票交易

                                                                                                          Ex­ample: Stock Trad­ing

                                                                                                          在股票交易系统中,用于执行交易请求的应用程序可能会收到当前报价的请求,或者未指定购买什么证券或多少股票的交易请求,或者未指定向谁购买的交易请求。发送交易确认。在任何这些情况下,应用程序都会收到无效消息,该消息不满足应用程序处理交易请求所需的最低要求。一旦应用程序确定消息无效,它应该将消息重新发送到无效消息通道。发送交易请求的各种应用程序可能希望监视无效消息通道以确定他们的请求是否被丢弃。

                                                                                                          In a stock trad­ing system, an ap­plic­a­tion for ex­ecut­ing trade re­quests might re­ceive a re­quest for a cur­rent price quote, or a trade re­quest that does not spe­cify what se­cur­ity to buy or how many shares, or a trade re­quest that does not spe­cify to whom to send the trade con­firm­a­tion. In any of these cases, the ap­plic­a­tion has re­ceived an in­valid mes­sageone that does not meet the min­imum re­quire­ments ne­ces­sary for the ap­plic­a­tion to be able to pro­cess the trade re­quest. Once the ap­plic­a­tion de­term­ines the mes­sage to be in­valid, it should resend the mes­sage onto the In­valid Mes­sage Chan­nel. The vari­ous ap­plic­a­tions that send trade re­quests may wish to mon­itor the In­valid Mes­sage Chan­nel to de­term­ine if their re­quests are being dis­carded.



                                                                                                          示例: JMS 规范

                                                                                                          Ex­ample: JMS Spe­cific­a­tion

                                                                                                          在 JMS 中,规范建议,如果 MessageListener收到无法处理的消息,则行为良好的侦听器应将消息转移到“某种形式的特定于应用程序的‘无法处理的消息’目的地”[JMS 1.1] 。 这个无法处理的消息目的地是无效消息通道

                                                                                                          In JMS, the spe­cific­a­tion sug­gests that if a Mes­sageL­istener gets a mes­sage it cannot pro­cess, a well-be­haved listener should divert the mes­sage "to some form of ap­plic­a­tion-spe­cific 'un­pro­cess­able mes­sage' des­tin­a­tion" [JMS 1.1]. This un­pro­cess­able mes­sage des­tin­a­tion is an In­valid Mes­sage Chan­nel.



                                                                                                          示例: 简单消息传递

                                                                                                          Ex­ample: Simple Mes­saging

                                                                                                          JMS 请求-答复示例和 .NET 请求-答复示例(均在第 6 章“插曲:简单消息传递”中)显示了如何实现接收器的示例,这些接收器将它们无法处理的消息重新路由到无效消息通道

                                                                                                          The JMS Re­quest-Reply ex­ample and .NET Re­quest-Reply ex­ample (both in Chapter 6, "In­ter­lude: Simple Mes­saging") show an ex­ample of how to im­ple­ment re­ceiv­ers that reroute mes­sages they cannot pro­cess to an In­valid Mes­sage Chan­nel.



                                                                                                            死信频道

                                                                                                            Dead Letter Channel

                                                                                                            图形/deadletter_icon.gif

                                                                                                            一家企业正在使用消息传递来集成应用程序。

                                                                                                            An en­ter­prise is using Mes­saging to in­teg­rate ap­plic­a­tions.

                                                                                                            消息系统将如何处理它无法传递的消息?

                                                                                                            What will the mes­saging system do with a mes­sage it cannot de­liver?



                                                                                                            如果接收方收到无法处理的消息,则应将无效消息移至无效消息通道。但是,如果消息传递系统无法首先将消息传递给接收者怎么办?

                                                                                                            If a re­ceiver re­ceives a mes­sage it cannot pro­cess, it should move the in­valid mes­sage to an In­valid Mes­sage Chan­nel. But what if the mes­saging system cannot de­liver the mes­sage to the re­ceiver in the first place?

                                                                                                            消息传递系统可能无法传递消息的原因有多种。消息传递系统可能没有正确配置消息的通道。消息的通道可以在消息发送之后但在传送之前或在等待接收时被删除。消息在传送之前可能会过期(请参阅消息过期) 。如果消息在很长一段时间内无法传送,则没有明确过期的消息可能会超时。具有所有选择性消费者都忽略的选择值的消息将永远不会被阅读,并且最终可能会消亡。消息的标头可能存在问题,导致其无法成功传递。

                                                                                                            There are a number of reas­ons the mes­saging system may not be able to de­liver a mes­sage. The mes­saging system may not have the mes­sage's chan­nel con­figured prop­erly. The mes­sage's chan­nel may be de­leted after the mes­sage is sent but before it can be de­livered or while it is wait­ing to be re­ceived. The mes­sage may expire before it can be de­livered (see Mes­sage Ex­pir­a­tion). A mes­sage without an ex­pli­cit ex­pir­a­tion may nev­er­the­less time out if it cannot be de­livered for a very long time. A mes­sage with a se­lec­tion value that all Se­lect­ive Con­sumers ignore will never be read and may even­tu­ally die. A mes­sage could have some­thing wrong with its header that pre­vents it from being de­livered suc­cess­fully.

                                                                                                            一旦消息传递系统确定它无法传递消息,它就必须对该消息执行某些操作。它可能会将消息留在任何地方,从而使系统变得混乱。它可以尝试将消息返回给发送者,但发送者不是接收者,无法检测到传递。它可能只是删除消息并希望没有人错过它,但这很可能会给已成功发送消息并期望消息被传递(并接收和处理)的发送者带来问题。

                                                                                                            Once the mes­saging system de­term­ines that it cannot de­liver a mes­sage, it has to do some­thing with the mes­sage. It could just leave the mes­sage wherever it is, clut­ter­ing up the system. It could try to return the mes­sage to the sender, but the sender is not a re­ceiver and cannot detect de­liv­er­ies. It could just delete the mes­sage and hope no one misses it, but this may well cause a prob­lem for the sender that has suc­cess­fully sent the mes­sage and ex­pects it to be de­livered (and re­ceived and pro­cessed).

                                                                                                            当消息传递系统确定它不能或不应该传递消息时,它可以选择将消息移动到死信通道

                                                                                                            When a mes­saging system de­term­ines that it cannot or should not de­liver a mes­sage, it may elect to move the mes­sage to a Dead Letter Chan­nel.

                                                                                                            图形/04inf07.gif



                                                                                                            死信通道的具体工作方式取决于特定消息传递系统的实现(如果它提供了一种实现)。该通道可以称为“死消息队列”[ Monson-Haefel ] 或“死信队列”[ MQSeries ] 、 [ Dickman ] 。通常,安装消息系统的每台机器都有自己的本地死信通道,因此无论消息在什么机器上死亡,它都可以从一个本地队列移动到另一个本地队列,而不会出现任何网络不确定性。这也记录了消息在哪台机器上死亡。当消息传递系统移动消息时,它还可以记录消息应该传递的原始通道。

                                                                                                            The spe­cific way a Dead Letter Chan­nel works de­pends on the spe­cific mes­saging system's im­ple­ment­a­tion, if it provides one at all. The chan­nel may be called a "dead mes­sage queue" [Monson-Haefel] or "dead letter queue" [MQSer­ies], [Dick­man]. Typ­ic­ally, each ma­chine the mes­saging system is in­stalled on has its own local Dead Letter Chan­nel so that whatever ma­chine a mes­sage dies on, it can be moved from one local queue to an­other without any net­work­ing un­cer­tain­ties. This also re­cords what ma­chine the mes­sage died on. When the mes­saging system moves the mes­sage, it may also record the ori­ginal chan­nel on which the mes­sage was sup­posed to be de­livered.

                                                                                                            死消息和无效消息之间的区别在于,消息传递系统无法成功传递它认为是死消息的消息,而无效消息可以正确传递,但无法被接收者处理。确定是否应将消息移至死信通道是由消息传递系统执行的对消息标头的评估。另一方面,接收方将消息移动到无效消息通道由于接收者感兴趣的消息正文或特定标头字段。对于接收者来说,死消息的确定和处理似乎是自动的,而接收者必须自行处理无效消息。使用消息传递系统的开发人员会被消息传递系统提供的任何无效消息处理所困扰,但她可以设计自己的无效消息处理,包括处理消息传递系统无法处理的看似无效的消息。

                                                                                                            The dif­fer­ence between a dead mes­sage and an in­valid one is that the mes­saging system cannot suc­cess­fully de­liver what it then deems a dead mes­sage, whereas an in­valid mes­sage is prop­erly de­livered but cannot be pro­cessed by the re­ceiver. De­term­in­ing if a mes­sage should be moved to the Dead Letter Chan­nel is an eval­u­ation of the mes­sage's header per­formed by the mes­saging system. On the other hand, the re­ceiver moves a mes­sage to an In­valid Mes­sage Chan­nel be­cause of the mes­sage's body or par­tic­u­lar header fields the re­ceiver is in­ter­ested in. To the re­ceiver, de­term­in­a­tion and hand­ling of dead mes­sages seem auto­matic, whereas the re­ceiver must handle in­valid mes­sages itself. A de­ve­loper using a mes­saging system is stuck with whatever dead mes­sage hand­ling the mes­saging system provides, but she can design her own in­valid mes­sage hand­ling, in­clud­ing hand­ling for seem­ingly dead mes­sages that the mes­saging system doesn't handle.

                                                                                                            示例: 股票交易

                                                                                                            Ex­ample: Stock Trad­ing

                                                                                                            在股票交易系统中,希望执行交易的应用程序可以发送交易请求。为了确保在合理的时间内(也许少于五分钟)收到交易,请求者将请求的消息过期时间设置为五分钟。如果消息传递系统无法在该时间内传递请求,或者交易应用程序没有及时接收消息(例如,从通道中读取消息),则消息传递系统将从交易请求中删除该消息通道并将消息放入死信通道。交易系统可能希望监视系统的死信通道以确定是否丢失了交易。

                                                                                                            In a stock trad­ing system, an ap­plic­a­tion that wishes to per­form a trade can send a trade re­quest. To make sure that the trade is re­ceived in a reas­on­able amount of time (less than five minutes, per­haps), the re­questor sets the re­quest's Mes­sage Ex­pir­a­tion to five minutes. If the mes­saging system cannot de­liver the re­quest in that amount of time, or if the trad­ing ap­plic­a­tion does not re­ceive the mes­sage (e.g., read it off of the chan­nel) in time, then the mes­saging system will take the mes­sage off of the trade re­quest chan­nel and put the mes­sage on the Dead Letter Chan­nel. The trad­ing system may wish to mon­itor the system's Dead Letter Chan­nels to de­term­ine if it is miss­ing trades.



                                                                                                              保证交付

                                                                                                              Guaranteed Delivery

                                                                                                              图形/guaranteeddelivery_icon.gif

                                                                                                              一家企业正在使用消息传递来集成应用程序。

                                                                                                              An en­ter­prise is using Mes­saging to in­teg­rate ap­plic­a­tions.

                                                                                                              即使消息系统出现故障,发送者如何确保消息能够送达?

                                                                                                              How can the sender make sure that a mes­sage will be de­livered even if the mes­saging system fails?



                                                                                                              与 RPC 相比,异步消息传递的主要优点之一是发送方、接收方以及连接两者的网络不必同时工作。如果网络不可用,消息传递系统会存储消息,直到网络可用。同样,如果接收者不可用,消息传递系统会存储消息并重试传递,直到接收者可用。这是消息传递所基于的存储和转发过程。那么,消息在转发之前应该存储在哪里呢?

                                                                                                              One of the main ad­vant­ages of asyn­chron­ous mes­saging over RPC is that the sender, the re­ceiver, and net­work con­nect­ing the two don't all have to be work­ing at the same time. If the net­work is not avail­able, the mes­saging system stores the mes­sage until the net­work be­comes avail­able. Like­wise, if the re­ceiver is un­avail­able, the mes­saging system stores the mes­sage and re­tries de­liv­ery until the re­ceiver be­comes avail­able. This is the store-and-for­ward pro­cess that mes­saging is based on. So, where should the mes­sage be stored before it is for­war­ded?

                                                                                                              默认情况下,消息传递系统将消息存储在内存中,直到它能够成功地将消息转发到下一个存储点。只要消息传递系统可靠运行,这种方法就有效,但如果消息传递系统崩溃(例如,由于其中一台计算机断电或消息传递进程意外中止),则存储在内存中的所有消息都会丢失。

                                                                                                              By de­fault, the mes­saging system stores the mes­sage in memory until it can suc­cess­fully for­ward the mes­sage to the next stor­age point. This works as long as the mes­saging system is run­ning re­li­ably, but if the mes­saging system crashes (for ex­ample, be­cause one of its com­puters loses power or the mes­saging pro­cess aborts un­ex­pec­tedly), all of the mes­sages stored in memory are lost.

                                                                                                              大多数应用程序都必须处理类似的问题。如果应用程序崩溃,存储在内存中的所有数据都会丢失。为了防止这种情况,应用程序使用文件和数据库将数据保存到磁盘,以便数据在系统崩溃时幸存下来。消息系统需要类似的方式来更持久地保存消息,这样即使系统崩溃也不会丢失消息。

                                                                                                              Most ap­plic­a­tions have to deal with sim­ilar prob­lems. All data that is stored in memory is lost if the ap­plic­a­tion crashes. To pre­vent this, ap­plic­a­tions use files and data­bases to per­sist data to disk so that it sur­vives system crashes. Mes­saging sys­tems need a sim­ilar way to per­sist mes­sages more per­man­ently so that no mes­sage gets lost even if the system crashes.

                                                                                                              使用保证传递使消息持久化,这样即使消息系统崩溃它们也不会丢失。

                                                                                                              Use Guar­an­teed De­liv­ery to make mes­sages per­sist­ent so that they are not lost even if the mes­saging system crashes.

                                                                                                              图形/04inf08.gif



                                                                                                              通过保证交付,消息传递系统使用内置数据存储来保存消息。每台安装消息系统的计算机都有自己的数据存储,以便可以在本地存储消息。当发送者发送消息时,只有在消息安全地存储在发送者的数据存储中时,发送操作才能成功完成。随后,消息不会从一个数据存储中删除,直到成功转发到并存储在下一个数据存储中。这样,一旦发送方成功发送消息,该消息将始终存储在至少一台计算机的磁盘上,直到消息成功传递给接收方并被接收方确认为止。

                                                                                                              With Guar­an­teed De­liv­ery, the mes­saging system uses a built-in data­store to per­sist mes­sages. Each com­puter on which the mes­saging system is in­stalled has its own data­store so that the mes­sages can be stored loc­ally. When the sender sends a mes­sage, the send op­er­a­tion does not com­plete suc­cess­fully until the mes­sage is safely stored in the sender's data­store. Sub­se­quently, the mes­sage is not de­leted from one data­store until it is suc­cess­fully for­war­ded to and stored in the next data­store. In this way, once the sender suc­cess­fully sends the mes­sage, it is always stored on disk on at least one com­puter until it is suc­cess­fully de­livered to and ac­know­ledged by the re­ceiver.

                                                                                                              持久性可提高可靠性,但会牺牲性能。因此,如果在消息传递系统崩溃或关闭时丢失消息是可以接受的,请避免使用保证传递,这样消息将更快地在消息传递系统中移动。

                                                                                                              Per­sist­ence in­creases re­li­ab­il­ity but at the ex­pense of per­form­ance. Thus, if it's okay to lose mes­sages when the mes­saging system crashes or is shut down, avoid using Guar­an­teed De­liv­ery so mes­sages will move through the mes­saging system faster.

                                                                                                              还要考虑到保证交付在高流量场景中可能会消耗大量磁盘空间。如果生产者每秒生成数百或数千条消息,那么持续数小时的网络中断可能会占用大量磁盘空间。由于网络不可用,消息必须存储在生产计算机的本地磁盘驱动器上,而该驱动器可能无法容纳这么多数据。由于这些原因,某些消息传递系统允许您配置重试超时指定消息在消息传递系统内缓冲多长时间的参数。在一些高流量应用中(例如,将股票报价流式传输到终端),该超时可能必须设置为较短的时间跨度,例如几分钟。幸运的是,在许多此类应用程序中,消息被用作事件消息,并且可以在短时间内安全地丢弃(请参阅消息过期)

                                                                                                              Also con­sider that Guar­an­teed De­liv­ery can con­sume a large amount of disk space in high-traffic scen­arios. If a pro­du­cer gen­er­ates hun­dreds or thou­sands of mes­sages per second, then a net­work outage that lasts mul­tiple hours could use up a huge amount of disk space. Be­cause the net­work is un­avail­able, the mes­sages have to be stored on the pro­du­cing com­puter's local disk drive, which may not be de­signed to hold this much data. For these reas­ons, some mes­saging sys­tems allow you to con­fig­ure a retry timeout para­meter that spe­cifies how long mes­sages are buf­fered inside the mes­saging system. In some high-traffic ap­plic­a­tions (e.g., stream­ing stock quotes to ter­min­als), this timeout may have to be set to a short time span, for ex­ample, a few minutes. Luck­ily, in many of these ap­plic­a­tions, mes­sages are used as Event Mes­sages and can safely be dis­carded after a short amount of time elapses (see Mes­sage Ex­pir­a­tion).

                                                                                                              在测试和调试期间关闭保证交付也很有用。这样可以通过停止并重新启动消息传递服务器轻松清除所有消息通道。即使是简单的消息传递程序,仍在排队的消息也会使调试变得非常乏味。例如,您可能有一个通过点对点通道连接的发送者和接收者。如果消息仍然存储在通道上,接收方将在发送方生成任何新消息之前处理该消息。这是异步、有保证的消息传递中的常见调试陷阱。许多商业消息传递实现还允许您单独清除队列,以便在测试期间重新启动(请参阅Channel Purger)。

                                                                                                              It can also be useful to turn off Guar­an­teed De­liv­ery during test­ing and de­bug­ging. This makes it easy to purge all mes­sage chan­nels by stop­ping and re­start­ing the mes­saging server. Mes­sages that are still queued up can make it very te­di­ous to debug even simple mes­saging pro­grams. For ex­ample, you may have a sender and a re­ceiver con­nec­ted by a Point-to-Point Chan­nel. If a mes­sage is still stored on the chan­nel, the re­ceiver will pro­cess that mes­sage before any new mes­sage that the sender pro­duces. This is a common de­bug­ging pit­fall in asyn­chron­ous, guar­an­teed mes­saging. Many com­mer­cial mes­saging im­ple­ment­a­tions also allow you to purge queues in­di­vidu­ally to allow a fresh re­start during test­ing (see Chan­nel Purger).

                                                                                                              保证消息传递的保证如何?

                                                                                                              How Guaranteed Is Guaranteed Messaging?

                                                                                                              重要的是要记住,计算机系统的可靠性往往以“9 的数量”来衡量,换句话说,99.9%。这告诉我们,有些东西很少是 100% 可靠的,成本已经呈指数级增长,从 99.9% 上升到 99.99%。同样的注意事项也适用于保证交付。总会有消息丢失的情况。例如,如果存储持久消息的磁盘发生故障,消息可能会丢失。您可以通过使用冗余磁盘存储来降低发生故障的可能性,从而使磁盘存储更加可靠。这可能会在可靠性评级上再增加一个“9”,但可能不会达到真正的 100%。另外,如果网络长时间不可用,必须存储的消息可能会填满计算机的磁盘,从而导致消息丢失。总之,保证传送旨在保护消息传送免受预期中断(例如机器故障或网络故障)的影响,但它通常不是 100% 万无一失。

                                                                                                              It is im­port­ant to keep in mind that re­li­ab­il­ity in com­puter sys­tems tends to be meas­ured in the "number of 9s"in other words, 99.9 per­cent. This tells us that some­thing is rarely 100 per­cent re­li­able, with the cost already in­creas­ing ex­po­nen­tially to move from 99.9 to 99.99 per­cent. The same caveats apply to Guar­an­teed De­liv­ery. There will always be a scen­ario where a mes­sage can get lost. For ex­ample, if the disk that stores the per­sisted mes­sages fails, mes­sages may get lost. You can make your disk stor­age more re­li­able by using re­dund­ant disk stor­age to reduce the like­li­hood of fail­ure. This will pos­sibly add an­other "9" to the re­li­ab­il­ity rating but likely not make it a true 100 per­cent. Also, if the net­work is un­avail­able for a long time, the mes­sages that have to be stored may fill up the com­puter's disk, res­ult­ing in lost mes­sages. In sum­mary, Guar­an­teed De­liv­ery is de­signed to pro­tect the mes­sage de­liv­ery from ex­pec­ted out­ages, such as ma­chine fail­ures or net­work fail­ures, but it is usu­ally not 100 per­cent bul­let­proof.



                                                                                                              对于 .NET 的 MSMQ 实现,要使通道持久化,必须将其声明为事务性的,这意味着发送方通常必须是事务性客户端。在 JMS 中,通过发布-订阅通道,保证交付仅确保消息将交付给活动订阅者。为了确保订阅者即使在不活动时也能收到消息,订阅者需要是持久订阅者

                                                                                                              With .NET's MSMQ im­ple­ment­a­tion, for a chan­nel to be per­sist­ent, it must be de­clared trans­ac­tional, which means senders usu­ally have to be Trans­ac­tional Cli­ents. In JMS, with Pub­lish-Sub­scribe Chan­nel, Guar­an­teed De­liv­ery only en­sures that the mes­sages will be de­livered to the active sub­scribers. To ensure that a sub­scriber re­ceives mes­sages even when it's in­act­ive, the sub­scriber will need to be a Dur­able Sub­scriber.

                                                                                                              示例: 股票交易

                                                                                                              Ex­ample: Stock Trad­ing

                                                                                                              在股票交易系统中,交易请求和交易确认可能应该通过保证交付来发送,以帮助确保不会丢失。地址变更公告应与保证交付 一起发送,但可能没有必要与价格更新一起发送,因为丢失其中一些通知并不重要,而且它们的频率使保证交付的开销令人望而却步。

                                                                                                              In a stock trad­ing system, trade re­quests and trade con­firm­a­tions should prob­ably be sent with Guar­an­teed De­liv­ery to help ensure that none are lost. Change-of-ad­dress an­nounce­ments should be sent with Guar­an­teed De­liv­ery, but it is prob­ably not ne­ces­sary with price up­dates be­cause losing some of them is not sig­ni­fic­ant, and their fre­quency makes the over­head of Guar­an­teed De­liv­ery pro­hib­it­ive.

                                                                                                              持久订阅者中,股票交易示例表明一些价格变化订阅者可能希望持久。如果是这样,那么也许价格变动渠道也应该保证交货。然而,其他订户可能不需要持久或希望承受保证交付的开销。如何满足这些不同的需求?系统可能希望实现两种价格更改渠道,一种具有保证交付,另一种则不具有。只有需要所有更新的订阅者才应该订阅持久通道,并且他们的订阅应该是持久的。由于开销增加,发布者可能希望在持久通道上不太频繁地发布更新。(参见服务质量通道策略在数据类型通道下讨论。

                                                                                                              In Dur­able Sub­scriber, the stock trad­ing ex­ample says that some price-change sub­scribers may wish to be dur­able. If so, then per­haps the price-change chan­nel should guar­an­tee de­liv­ery as well. Yet other sub­scribers may not need to be dur­able or want to suffer the over­head of Guar­an­teed De­liv­ery. How can these dif­fer­ent needs be met? The system may wish to im­ple­ment two price-change chan­nels, one with Guar­an­teed De­liv­ery and an­other without. Only sub­scribers that re­quire all up­dates should sub­scribe to the per­sist­ent chan­nel, and their sub­scrip­tions should be dur­able. The pub­lisher may wish to pub­lish up­dates less fre­quently on the per­sist­ent chan­nel be­cause of its in­creased over­head. (See the Qual­ity-of-Ser­vice Chan­nel strategy dis­cussed under Data­type Chan­nel.)



                                                                                                              示例: JMS 持久消息

                                                                                                              Ex­ample: JMS Per­sist­ent Mes­sages

                                                                                                              在 JMS 中,可以基于每个消息设置消息持久性。换句话说,特定通道上的某些消息可能是持久的,而其他消息可能不是[ JMS 1.1 ]、[ Hapner ]。

                                                                                                              In JMS, mes­sage per­sist­ence can be set on a per-mes­sage basis. In other words, some mes­sages on a par­tic­u­lar chan­nel may be per­sist­ent, whereas others might not be [JMS 1.1], [Hapner].

                                                                                                              当 JMS 发送方想要使消息持久化时,它使用其MessageProducer 将消息为 PERSISTENT 。 发送者可以按消息设置持久性,如下所示:

                                                                                                              When a JMS sender wants to make a mes­sage per­sist­ent, it uses its Mes­sage­Pro­du­cer to set the mes­sage's JMS­De­liv­ery­Mode to PER­SIST­ENT. The sender can set per­sist­ence on a per-mes­sage basis like this:

                                                                                                              session session = //获取session
                                                                                                              目的地目的地= //获取目的地
                                                                                                              Message message = // 创建消息
                                                                                                              MessageProducer 生产者 = session.createProducer(destination);
                                                                                                              生产者.发送(
                                                                                                                  信息,
                                                                                                                  javax.jms.DeliveryMode.PERSISTENT,
                                                                                                                  javax.jms.Message.DEFAULT_PRIORITY,
                                                                                                                  javax.jms.Message.DEFAULT_TIME_TO_LIVE);
                                                                                                              
                                                                                                              Ses­sion ses­sion = // obtain the ses­sion
                                                                                                              Des­tin­a­tion des­tin­a­tion = // obtain the des­tin­a­tion
                                                                                                              Mes­sage mes­sage = // create the mes­sage
                                                                                                              Mes­sage­Pro­du­cer pro­du­cer = ses­sion.cre­ate­Pro­du­cer(des­tin­a­tion);
                                                                                                              pro­du­cer.send(
                                                                                                                  mes­sage,
                                                                                                                  javax.jms.De­liv­ery­Mode.PER­SIST­ENT,
                                                                                                                  javax.jms.Mes­sage.DE­FAULT_­PRI­OR­ITY,
                                                                                                                  javax.jms.Mes­sage.DE­FAULT_­TIME_TO_LIVE);
                                                                                                              

                                                                                                              如果应用程序希望使所有消息持久化,则可以将其设置为消息生产者的默认值。

                                                                                                              If the ap­plic­a­tion wants to make all of the mes­sages per­sist­ent, it can set that as the de­fault for the mes­sage pro­du­cer.

                                                                                                              Producer.setDeliveryMode(javax.jms.DeliveryMode.PERSISTENT);
                                                                                                              
                                                                                                              pro­du­cer.set­De­liv­ery­Mode(javax.jms.De­liv­ery­Mode.PER­SIST­ENT);
                                                                                                              

                                                                                                              (事实上​​,消息生产者的默认传递模式是持久化的。)现在,该生产者发送的消息会自动持久化,因此可以简单地发送它们。

                                                                                                              (And, in fact, the de­fault de­liv­ery mode for a mes­sage pro­du­cer is per­sist­ent.) Now, mes­sages sent by this pro­du­cer are auto­mat­ic­ally per­sist­ent, so they can simply be sent.

                                                                                                              生产者.发送(消息);
                                                                                                              
                                                                                                              pro­du­cer.send(mes­sage);
                                                                                                              

                                                                                                              同时,同一通道上的其他消息生产者发送的消息可能是持久的,具体取决于这些生产者如何配置其消息。

                                                                                                              Mean­while, mes­sages sent by other mes­sage pro­du­cers on the same chan­nel may be per­sist­ent, de­pend­ing on how those pro­du­cers con­fig­ure their mes­sages.



                                                                                                              示例: IBM WebSphere MQ

                                                                                                              Ex­ample: IBM Web­Sphere MQ

                                                                                                              在 WebSphere MQ 中,可以基于每个通道或每个消息设置保证交付。如果通道不是持久化的,那么消息就不能持久化。如果通道是持久的,则可以配置通道,使得在该通道上发送的所有消息自动持久,或者可以持久或非持久地发送单独的消息。

                                                                                                              In Web­Sphere MQ, Guar­an­teed De­liv­ery can be set on a per-chan­nel basis or a per-mes­sage basis. If the chan­nel is not per­sist­ent, the mes­sages cannot be per­sist­ent. If the chan­nel is per­sist­ent, the chan­nel can be con­figured such that all mes­sages sent on that chan­nel are auto­mat­ic­ally per­sist­ent or that an in­di­vidual mes­sage can be sent per­sist­ently or non­per­sist­ently.

                                                                                                              当在消息传递系统中创建通道时,通道被配置为持久(或非持久)。例如,可以配置通道以使其所有消息都是持久的。

                                                                                                              A chan­nel is con­figured to be per­sist­ent (or not) when it is cre­ated in the mes­saging system. For ex­ample, the chan­nel can be con­figured so that all of its mes­sages will be per­sist­ent.

                                                                                                              定义 Q(myQueue) PER(PERS)
                                                                                                              
                                                                                                              DEFINE Q(myQueue) PER(PERS)
                                                                                                              

                                                                                                              或者可以配置通道,以便消息发送者可以为每条消息指定该消息是持久的还是瞬态的。

                                                                                                              Or the chan­nel can be con­figured so that the mes­sage sender can spe­cify with each mes­sage whether the mes­sage is per­sist­ent or tran­si­ent.

                                                                                                              定义 Q(myQueue) PER(APP)
                                                                                                              
                                                                                                              DEFINE Q(myQueue) PER(APP)
                                                                                                              

                                                                                                              如果通道设置为允许发送者指定持久性,那么 JMS MessageProducer 可以设置该传递模式属性,如前所述。如果通道设置为使所有消息持久化,则 MessageProducer 指定的传递模式设置将被忽略[ WSMQ ] 。

                                                                                                              If the chan­nel is set to allow the sender to spe­cify per­sist­ency, then a JMS Mes­sage­Pro­du­cer can set that de­liv­ery-mode prop­erty as de­scribed earlier. If the chan­nel is set to make all mes­sages per­sist­ent, then the de­liv­ery-mode set­tings spe­cified by the Mes­sage­Pro­du­cer are ig­nored [WSMQ].



                                                                                                              示例: .NET 持久消息

                                                                                                              Ex­ample: .NET Per­sist­ent Mes­sages

                                                                                                              在 .NET 中,持久消息是通过将MessageQueue 设为事务性来创建的。

                                                                                                              With .NET, per­sist­ent mes­sages are cre­ated by making a Mes­sageQueue trans­ac­tional.

                                                                                                              MessageQueue.Create("MyQ​​ueue", true);
                                                                                                              
                                                                                                              Mes­sageQueue.Create("MyQueue", true);
                                                                                                              

                                                                                                              在此队列上发送的所有消息将自动持久化 [ Dickman ]

                                                                                                              All mes­sages sent on this queue will auto­mat­ic­ally be per­sist­ent [Dick­man].



                                                                                                                通道适配器

                                                                                                                Channel Adapter

                                                                                                                图形/channeladapter_icon.gif

                                                                                                                许多企业使用消息传递来集成多个不同的应用程序。

                                                                                                                Many en­ter­prises use Mes­saging to in­teg­rate mul­tiple, dis­par­ate ap­plic­a­tions.

                                                                                                                如何将应用程序连接到消息传递系统以便它可以发送和接收消息?

                                                                                                                How can you con­nect an ap­plic­a­tion to the mes­saging system so that it can send and re­ceive mes­sages?



                                                                                                                大多数应用程序并非设计用于与消息传递基础设施配合使用。造成这种限制的原因有多种。许多应用程序都是作为独立的独立解决方案开发的,尽管它们包含可由其他系统利用的数据或功能。例如,许多大型机应用程序被设计为一体化应用程序,永远不需要与其他应用程序交互。唉,遗留集成现在是企业集成解决方案最常见的集成点之一。另一个原因是许多面向消息的中间件系统公开专有的 API,因此应用程序开发人员必须编写与消息传递系统的多个接口,

                                                                                                                Most ap­plic­a­tions were not de­signed to work with a mes­saging in­fra­struc­ture. There are a vari­ety of reas­ons for this lim­it­a­tion. Many ap­plic­a­tions were de­ve­loped as self-con­tained, stan­dalone solu­tions even though they con­tain data or func­tion­al­ity that can be lever­aged by other sys­tems. For ex­ample, many main­frame ap­plic­a­tions were de­signed as a one-in-all ap­plic­a­tion that would never have to in­ter­face with other ap­plic­a­tions. Alas, legacy in­teg­ra­tion is nowadays one of the most common in­teg­ra­tion points for en­ter­prise in­teg­ra­tion solu­tions. An­other reason res­ults from the fact that many mes­sage-ori­ented mid­dle­ware sys­tems expose pro­pri­et­ary APIs so that an ap­plic­a­tion de­ve­loper would have to code mul­tiple in­ter­faces to the mes­saging system, one for each po­ten­tial mid­dle­ware vendor.

                                                                                                                如果应用程序需要与其他应用程序交换数据,它们通常被设计为使用更通用的接口机制,例如文件交换或数据库表。读写文件是一项基本的操作系统功能,不依赖于特定于供应商的 API。同样,大多数业务应用程序已经将数据保存到数据库中,因此只需很少的额外工作即可将发往其他系统的数据存储在数据库表中。或者,应用程序可以在通用 API 中公开内部函数,供任何其他集成策略(包括消息传递)使用。

                                                                                                                If ap­plic­a­tions need to ex­change data with other ap­plic­a­tions, they often are de­signed to use more gen­eric in­ter­face mech­an­isms such as file ex­change or data­base tables. Read­ing and writ­ing files is a basic op­er­at­ing system func­tion and does not depend on vendor-spe­cific APIs. Like­wise, most busi­ness ap­plic­a­tions already per­sist data into a data­base, so little extra effort is re­quired to store data destined for other sys­tems in a data­base table. Or an ap­plic­a­tion can expose in­ternal func­tions in a gen­eric API that can be used by any other in­teg­ra­tion strategy, in­clud­ing mes­saging.

                                                                                                                其他应用程序可能能够通过 HTTP 或 TCP/IP 等简单协议进行通信。然而,这些协议不提供与消息通道相同的可靠性,并且应用程序使用的数据格式通常特定于应用程序并且与通用消息传递解决方案不兼容。

                                                                                                                Other ap­plic­a­tions may be cap­able of com­mu­nic­at­ing via a simple pro­tocol like HTTP or TCP/IP. How­ever, these pro­to­cols do not provide the same re­li­ab­il­ity as a Mes­sage Chan­nel, and the data format used by the ap­plic­a­tion is usu­ally spe­cific to the ap­plic­a­tion and not com­pat­ible with a common mes­saging solu­tion.

                                                                                                                对于自定义应用程序,我们可以向应用程序添加代码以允许其发送和接收消息。然而,这可能会给应用程序带来额外的复杂性,我们需要小心,在进行这些更改时不要引入任何不需要的副作用。此外,这种方法要求开发人员精通应用程序逻辑和消息传递 API。这两种方法还假设我们可以访问应用程序源代码。如果我们处理从第三方软件供应商购买的打包应用程序,我们甚至可能无法选择更改应用程序代码。

                                                                                                                In the case of custom ap­plic­a­tions, we could add code to the ap­plic­a­tion to allow it to send and re­ceive mes­sages. How­ever, this can in­tro­duce ad­di­tional com­plex­ity into the ap­plic­a­tion and we need to be care­ful not to in­tro­duce any un­de­sired side ef­fects when making these changes. Also, this ap­proach re­quires de­ve­lopers to be skilled with both the ap­plic­a­tion logic and the mes­saging API. Both those ap­proaches also assume that we have access to the ap­plic­a­tion source code. If we deal with a pack­aged ap­plic­a­tion that we pur­chased from a third-party soft­ware vendor, we may not even have the option of chan­ging the ap­plic­a­tion code.

                                                                                                                使用可以访问应用程序的 API 或数据的通道适配器,以基于此数据在通道上发布消息,并且同样可以接收消息并调用应用程序内部的功能。

                                                                                                                Use a Chan­nel Ad­apter that can access the ap­plic­a­tion's API or data to pub­lish mes­sages on a chan­nel based on this data and that like­wise can re­ceive mes­sages and invoke func­tion­al­ity inside the ap­plic­a­tion.

                                                                                                                图形/04inf09.gif



                                                                                                                适配器充当消息传递系统的消息传递客户端,并通过应用程序提供的接口调用应用程序功能。同样,通道适配器可以侦听应用程序内部事件并调用消息传递系统来响应这些事件。这样,任何应用程序只要有适当的Channel Adapter 就可以连接到消息系统并与其他应用程序集成

                                                                                                                The ad­apter acts as a mes­saging client to the mes­saging system and in­vokes ap­plic­a­tion func­tions via an ap­plic­a­tion-sup­plied in­ter­face. Like­wise, the Chan­nel Ad­apter can listen to ap­plic­a­tion-in­ternal events and invoke the mes­saging system in re­sponse to these events. This way, any ap­plic­a­tion can con­nect to the mes­saging system and be in­teg­rated with other ap­plic­a­tions as long as it has a proper Chan­nel Ad­apter.

                                                                                                                通道适配器可以连接到应用程序体系结构的不同层,具体取决于该体系结构和消息传递系统需要访问的数据类型。

                                                                                                                The Chan­nel Ad­apter can con­nect to dif­fer­ent layers of the ap­plic­a­tion's ar­chi­tec­ture, de­pend­ing on that ar­chi­tec­ture and the type of data the mes­saging system needs to access.

                                                                                                                连接到应用程序不同层的通道适配器

                                                                                                                A Chan­nel Ad­apter Con­nect­ing to Dif­fer­ent Layers of an Ap­plic­a­tion

                                                                                                                图形/04inf10.gif

                                                                                                                1. 用户界面适配器。 这些类型的适配器有时被轻蔑地称为“屏幕抓取”,在许多情况下都非常有效。例如,应用程序可能在消息系统不支持的平台上实现。或者应用程序的所有者可能对支持集成兴趣不大。这消除了运行通道适配器的选项在应用平台上。然而,用户界面通常可从其他机器和平台(例如,3270 终端)获得。此外,基于 Web 的瘦客户端架构的兴起也引起了用户界面集成的一定程度的复兴。基于 HTML 的用户界面使得发出 HTTP 请求并解析结果变得非常容易。用户界面集成的另一个优点是不需要直接访问应用程序内部。在某些情况下,可能不希望或不可能将系统的内部功能暴露给集成解决方案。使用用户界面适配器,其他应用程序可以像普通用户一样访问该应用程序。用户界面适配器的缺点是解决方案潜在的脆弱性和低速度。应用程序必须解析“用户”输入并呈现屏幕作为响应,以便通道适配器可以将屏幕解析回原始数据。此过程涉及许多不必要的步骤,而且可能很慢。此外,用户界面的变化往往比核心应用程序逻辑更频繁。每次用户界面发生变化时,通道适配器可能也必须更改。

                                                                                                                2. User In­ter­face Ad­apter. Some­times dis­par­agingly called "screen scrap­ing," these types of ad­apters can be very ef­fect­ive in many situ­ations. For ex­ample, an ap­plic­a­tion may be im­ple­men­ted on a plat­form that is not sup­por­ted by the mes­saging system. Or the owner of the ap­plic­a­tion may have little in­terest in sup­port­ing the in­teg­ra­tion. This elim­in­ates the option of run­ning the Chan­nel Ad­apter on the ap­plic­a­tion plat­form. How­ever, the user in­ter­face is usu­ally avail­able from other ma­chines and plat­forms (e.g., 3270 ter­min­als). Also, the surge of Web-based thin-client ar­chi­tec­tures has caused a cer­tain re­vival of user in­ter­face in­teg­ra­tion. HTML-based user in­ter­faces make it very easy to make an HTTP re­quest and parse out the res­ults. An­other ad­vant­age of user in­ter­face in­teg­ra­tion is that no direct access to the ap­plic­a­tion in­tern­als is needed. In some cases, it may not be de­sir­able or pos­sible to expose in­ternal func­tions of a system to the in­teg­ra­tion solu­tion. Using a user in­ter­face ad­apter, other ap­plic­a­tions have the exact same access to the ap­plic­a­tion as a reg­u­lar user. The down­side of user in­ter­face ad­apters is the po­ten­tial brit­tle­ness and low speed of the solu­tion. The ap­plic­a­tion has to parse "user" input and render a screen in re­sponse just so that the Chan­nel Ad­apter can parse the screen back into raw data. This pro­cess in­volves many un­ne­ces­sary steps, and it can be slow. Also, user in­ter­faces tend to change more fre­quently than the core ap­plic­a­tion logic. Every time the user in­ter­face changes, the Chan­nel Ad­apter is likely to have to be changed as well.

                                                                                                                3. 业务逻辑适配器。 大多数业务应用程序将其核心功能公开为 API。该接口可以是一组组件(例如,EJB、COM 对象、CORBA 组件)或直接编程API(例如,C++、C# 或Java 库)。由于软件供应商(或开发人员)明确公开这些 API 供其他应用程序访问,因此它们往往比用户界面更稳定。在大多数情况下,访问 API 也更加高效。一般来说,如果应用程序公开定义良好的 API,那么这种类型的通道适配器可能是最好的方法。

                                                                                                                4. Busi­ness Logic Ad­apter. Most busi­ness ap­plic­a­tions expose their core func­tions as an API. This in­ter­face may be a set of com­pon­ents (e.g., EJBs, COM ob­jects, CORBA com­pon­ents) or a direct pro­gram­ming API (e.g., a C++, C#, or Java lib­rary). Since the soft­ware vendor (or de­ve­loper) ex­poses these APIs ex­pressly for access by other ap­plic­a­tions, they tend to be more stable than the user in­ter­face. In most cases, ac­cess­ing the API is also more ef­fi­cient. In gen­eral, if the ap­plic­a­tion ex­poses a well-defined API, this type of Chan­nel Ad­apter is likely to be the best ap­proach.

                                                                                                                5. 数据库适配器。 大多数业务应用程序将其数据保存在关系数据库中。由于信息已经在数据库中,因此通道适配器可以直接从数据库中提取信息,而应用程序不会注意到,这是一种非常非侵入性的应用程序集成方式。Channel Adapter甚至可以向相关表添加触发器,并在每次这些表中的数据发生变化时发送消息。这种类型的通道适配器由于只有两三个数据库供应商主导了关系数据库市场,这一事实可能非常高效并且非常普遍。这使我们能够使用相对通用的适配器连接到许多不同的应用程序。数据库适配器的缺点是我们要深入研究应用程序的内部。如果我们只是读取数据,这可能没有那么危险,但直接更新数据库可能非常危险。此外,许多应用程序供应商认为数据库模式“未发布”,这意味着他们保留随意更改它的权利,这可能会使数据库适配器解决方案变得脆弱。

                                                                                                                6. Data­base Ad­apter. Most busi­ness ap­plic­a­tions per­sist their data inside a re­la­tional data­base. Since the in­form­a­tion is already in the data­base, the Chan­nel Ad­apter can ex­tract in­form­a­tion dir­ectly from the data­base without the ap­plic­a­tion ever no­ti­cing, which is a very non­in­trus­ive way to in­teg­rate the ap­plic­a­tion. The Chan­nel Ad­apter can even add a trig­ger to the rel­ev­ant tables and send mes­sages every time the data in these tables changes. This type of Chan­nel Ad­apter can be very ef­fi­cient and is quite uni­ver­sal, aided by the fact that only two or three data­base vendors dom­in­ate the market for re­la­tional data­bases. This allows us to con­nect to many dif­fer­ent ap­plic­a­tions with a re­l­at­ively gen­eric ad­apter. The down­side of a data­base ad­apter is that we are poking around deep in the in­tern­als of an ap­plic­a­tion. This may not be as risky if we simply read data, but making up­dates dir­ectly to the data­base can be very dan­ger­ous. Also, many ap­plic­a­tion vendors con­sider the data­base schema "un­pub­lished," mean­ing that they re­serve the right to change it at will, which can make a data­base ad­apter solu­tion brittle.

                                                                                                                Channel Adapter的一个重要限制是它们可以将消息转换为应用程序函数,但它们需要与所适配的组件的实现非常相似的消息格式。例如,数据库适配器通常要求传入消息的消息字段名称与应用程序数据库中的表和字段的名称相同。这种消息格式完全由应用程序的内部结构驱动,在与其他应用程序集成时不是一个好的消息格式。因此,大多数Channel Adapter必须与Message将应用程序特定的消息转换为符合规范数据模型的消息格式。

                                                                                                                An im­port­ant lim­it­a­tion of Chan­nel Ad­apters is that they can con­vert mes­sages into ap­plic­a­tion func­tions, but they re­quire mes­sage format­ting that closely re­sembles the im­ple­ment­a­tion of the com­pon­ents being ad­ap­ted. For ex­ample, a data­base ad­apter typ­ic­ally re­quires the mes­sage field names of in­com­ing mes­sages to be the same as the names of tables and fields in the ap­plic­a­tion data­base. This kind of mes­sage format is driven en­tirely by the in­ternal struc­ture of the ap­plic­a­tion and is not a good mes­sage format to use when in­teg­rat­ing with other ap­plic­a­tions. There­fore, most Chan­nel Ad­apters must be com­bined with a Mes­sage Trans­lator to con­vert the ap­plic­a­tion-spe­cific mes­sage into a mes­sage format that com­plies with the Ca­non­ical Data Model.

                                                                                                                通道适配器通常可以在与应用程序或数据库本身不同的计算机上运行。通道适配器可以通过 HTTP 或 ODBC 等协议连接到应用程序逻辑或数据库。虽然这种设置允许我们避免在应用程序或数据库服务器上安装额外的软件,但这些协议通常不提供与消息传递通道相同的服务质量,例如保证交付。因此,我们必须意识到与数据库的远程连接可能代表潜在的故障点。

                                                                                                                Chan­nel Ad­apters can often run on a dif­fer­ent com­puter than the ap­plic­a­tion or the data­base itself. The Chan­nel Ad­apter can con­nect to the ap­plic­a­tion logic or the data­base via pro­to­cols such as HTTP or ODBC. While this setup allows us to avoid in­stalling ad­di­tional soft­ware on the ap­plic­a­tion or data­base server, these pro­to­cols typ­ic­ally do not provide the same qual­ity of ser­vice that a mes­saging chan­nel provides, such as guar­an­teed de­liv­ery. There­fore, we must be aware that the remote con­nec­tion to the data­base can rep­res­ent a po­ten­tial point of fail­ure.

                                                                                                                有些Channel Adapter是单向的。例如,如果Channel Adapter 通过 HTTP连接到应用程序,它可能只能消费消息并调用应用程序上的函数,但它可能无法检测应用程序数据的变化,除非通过重复轮询,这可以是效率很低。

                                                                                                                Some Chan­nel Ad­apters are uni­direc­tional. For ex­ample, if a Chan­nel Ad­apter con­nects to an ap­plic­a­tion via HTTP, it may only be able to con­sume mes­sages and invoke func­tions on the ap­plic­a­tion, but it may not be able to detect changes in the ap­plic­a­tion data except through re­peated polling, which can be very in­ef­fi­cient.

                                                                                                                通道适配器的一个有趣的变体元数据适配器,有时称为设计时适配器。这种类型的适配器不调用应用程序功能,而是提取元数据,即描述应用程序内部数据格式的数据。然后,该元数据可用于配置消息转换器或检测应用程序数据格式的更改(请参阅第 8 章中的介绍),“消息转换”)。许多应用程序接口支持这种类型的元数据提取。例如,大多数商业数据库提供一组系统表,其中包含应用程序表描述形式的元数据。同样,大多数组件框架(例如,J2EE、.NET)都提供特殊的“反射”功能,允许组件枚举另一个组件提供的方法。

                                                                                                                An in­ter­est­ing vari­ation of the Chan­nel Ad­apter is the Metadata Ad­apter, some­times called Design-Time Ad­apter. This type of ad­apter does not invoke ap­plic­a­tion func­tions but ex­tracts metadata, data that de­scribes the in­ternal data formats of the ap­plic­a­tion. This metadata can then be used to con­fig­ure Mes­sage Trans­lat­ors or to detect changes in the ap­plic­a­tion data formats (see the in­tro­duc­tion in Chapter 8, "Mes­sage Trans­form­a­tion"). Many ap­plic­a­tion in­ter­faces sup­port this type of metadata ex­trac­tion. For ex­ample, most com­mer­cial data­bases provide a set of system tables that con­tain metadata in the form of de­scrip­tions of the ap­plic­a­tion tables. Like­wise, most com­pon­ent frame­works (e.g., J2EE, .NET) provide spe­cial "re­flec­tion" func­tions that allow a com­pon­ent to enu­mer­ate meth­ods provided by an­other com­pon­ent.

                                                                                                                通道适配器的一种特殊形式消息传递桥。消息传递桥将消息传递系统连接到另一个消息传递系统,而不是特定的应用程序。通常,通道适配器被实现为事务客户端,以确保适配器所做的每一项工作在消息传递系统和正在适应的其他系统中都成功。

                                                                                                                A spe­cial form of the Chan­nel Ad­apter is the Mes­saging Bridge. The Mes­saging Bridge con­nects the mes­saging system to an­other mes­saging system as op­posed to a spe­cific ap­plic­a­tion. Typ­ic­ally, a Chan­nel Ad­apter is im­ple­men­ted as a Trans­ac­tional Client to ensure that each piece of work the ad­apter does suc­ceeds in both the mes­saging system and the other system being ad­ap­ted.

                                                                                                                示例: 股票交易

                                                                                                                Ex­ample: Stock Trad­ing

                                                                                                                股票交易系统可能希望在数据库表中保存所有股票价格的日志。消息传送系统可以包括关系数据库适配器,其将来自通道的每条消息记录到指定的表和模式。此通道到 RDBMS 的适配器是Channel Adapter 。系统还能够从互联网(TCP/IP或HTTP)接收外部报价请求,并将它们与内部报价请求一起在其内部报价请求通道上发送。此 Internet 到通道适配器是Channel Adapter

                                                                                                                A stock trad­ing system may wish to keep a log of all of a stocks' prices in a data­base table. The mes­saging system may in­clude a re­la­tional data­base ad­apter that logs each mes­sage from a chan­nel to a spe­cified table and schema. This chan­nel-to-RDBMS ad­apter is a Chan­nel Ad­apter. The system may also be able to re­ceive ex­ternal quote re­quests from the In­ter­net (TCP/IP or HTTP) and send them on its in­ternal quote-re­quest chan­nel with the in­ternal quote re­quests. This In­ter­net-to-chan­nel ad­apter is a Chan­nel Ad­apter.



                                                                                                                示例: 商业 EAI 工具

                                                                                                                Ex­ample: Com­mer­cial EAI Tools

                                                                                                                商业 EAI 供应商提供了一系列Channel Adapter作为其产品的一部分。拥有适用于所有可用主要应用程序包的适配器大大简化了集成解决方案的开发。大多数供应商还提供更通用的数据库适配器以及软件开发工具包 (SDK) 来开发自定义适配器。

                                                                                                                Com­mer­cial EAI vendors provide a col­lec­tion of Chan­nel Ad­apters as part of their of­fer­ings. Having ad­apters to all major ap­plic­a­tion pack­ages avail­able greatly sim­pli­fies de­vel­op­ment of an in­teg­ra­tion solu­tion. Most vendors also provide more gen­eric data­base ad­apters as well as soft­ware de­vel­op­ment kits (SDKs) to de­velop custom ad­apters.



                                                                                                                示例: 旧平台适配器

                                                                                                                Ex­ample: Legacy Plat­form Ad­apters

                                                                                                                许多供应商提供从通用消息传递系统到在 UNIX、MVS、OS/2、AS/400、Unisys 和 VMS 等平台上执行的遗留系统的适配器。这些适配器中的大多数都是特定于某个消息传递系统的。例如,Envoy Technologies 的 EnvoyMQ 是一个通道适配器,可将许多传统平台与 MSMQ 连接起来。它由在旧计算机上运行的客户端组件和在带有 MSMQ 的 Windows 计算机上运行的服务器组件组成。

                                                                                                                A number of vendors provide ad­apters from common mes­saging system to legacy sys­tems ex­ecut­ing on plat­forms such as UNIX, MVS, OS/2, AS/400, Unisys, and VMS. Most of these ad­apters are spe­cific to a cer­tain mes­saging system. For ex­ample, Envoy Tech­no­lo­gies' En­voyMQ is a Chan­nel Ad­apter that con­nects many legacy plat­forms with MSMQ. It con­sists of a client com­pon­ent that runs on the legacy com­puter and a server com­pon­ent that runs on a Win­dows com­puter with MSMQ.



                                                                                                                示例: Web 服务适配器

                                                                                                                Ex­ample: Web Ser­vices Ad­apters

                                                                                                                许多消息传递系统提供通道适配器来在 HTTP 传输和消息传递系统之间移动 SOAP 消息。这样,SOAP 消息就可以使用可靠的异步消息传递系统在 Intranet 上传输,并使用 HTTP 在全球 Internet(并穿过防火墙)上传输。IBM 的 WebSphere Application Server 的 Web 服务网关就是此类适配器的一个示例。

                                                                                                                Many mes­saging sys­tems provide Chan­nel Ad­apters to move SOAP mes­sages between an HTTP trans­port and the mes­saging system. This way, SOAP mes­sages can be trans­mit­ted over an in­tranet using a re­li­able, asyn­chron­ous mes­saging system and over the global In­ter­net (and through fire­walls) using HTTP. One ex­ample of such an ad­apter is the Web Ser­vices Gate­way for IBM's Web­Sphere Ap­plic­a­tion Server.



                                                                                                                  消息桥

                                                                                                                  Messaging Bridge

                                                                                                                  图形/messagingbridge_icon.gif

                                                                                                                  企业正在使用消息传递来使应用程序能够进行通信。然而,企业使用多个消息系统,这使得应用程序应该连接到哪个消息系统的问题变得混乱。

                                                                                                                  An en­ter­prise is using Mes­saging to enable ap­plic­a­tions to com­mu­nic­ate. How­ever, the en­ter­prise uses more than one mes­saging system, which con­fuses the issue of which mes­saging system an ap­plic­a­tion should con­nect to.

                                                                                                                  如何连接多个消息传递系统,以便一个系统上可用的消息在其他系统上也可用?

                                                                                                                  How can mul­tiple mes­saging sys­tems be con­nec­ted so that mes­sages avail­able on one are also avail­able on the others?



                                                                                                                  一个常见问题是企业使用多个消息传递系统。发生这种情况的原因是两家不同的公司之间进行了合并或收购,而这两家公司已经针对不同的消息传递产品进行了标准化。有时,使用一个消息系统来集成其大型机/遗留系统的单个企业会选择另一个系统作为其 J2EE 或 .NET Web 应用程序服务器,然后需要集成这两个消息系统。另一种常见情况是作为多个企业一部分参与的应用程序,例如希望成为多个拍卖系统中的投标人的 B2B 客户。如果各个拍卖集群使用不同的消息系统,企业内的投标人应用程序可能希望将来自多个外部消息传送系统的消息合并到单个内部消息传送系统上。另一个例子是拥有大量人员的超大型企业。消息通道消息端点可能

                                                                                                                  A common prob­lem is an en­ter­prise that uses more than one mes­saging system. This can occur be­cause of a merger or ac­quis­i­tion between two dif­fer­ent com­pan­ies that have stand­ard­ized around dif­fer­ent mes­saging products. Some­times a single en­ter­prise that uses one mes­saging system to in­teg­rate its main­frame/legacy sys­tems chooses an­other for its J2EE or .NET Web ap­plic­a­tion serv­ers and then needs to in­teg­rate the two mes­saging sys­tems. An­other common oc­cur­rence is an ap­plic­a­tion that par­ti­cip­ates as part of mul­tiple en­ter­prises, such as a B2B client that wants to be a bidder in mul­tiple auc­tion­ing sys­tems. If the vari­ous auc­tion clusters use dif­fer­ent mes­saging sys­tems, the bidder ap­plic­a­tions within an en­ter­prise may wish to con­sol­id­ate the mes­sages from sev­eral ex­ternal mes­saging sys­tems onto a single in­ternal mes­saging system. An­other ex­ample is the ex­tremely large en­ter­prise with a huge number of Mes­sage Chan­nels and Mes­sage En­d­points that may re­quire more than one in­stance of the mes­saging system, which means those in­stances must be con­nec­ted some­how.

                                                                                                                  如果一个系统上的消息对使用另一消息系统的应用程序不感兴趣,则这些系统可以保持完全独立。但由于这些应用程序是同一企业的一部分,因此使用一个消息传递系统的某些应用程序通常会对在另一消息传递系统上传输的消息感兴趣。

                                                                                                                  If the mes­sages on one system are of no in­terest to the ap­plic­a­tions using the other mes­saging system, then the sys­tems can remain com­pletely sep­ar­ate. But be­cause the ap­plic­a­tions are part of the same en­ter­prise, often some ap­plic­a­tions using one mes­saging system will be in­ter­ested in mes­sages being trans­mit­ted on an­other mes­saging system.

                                                                                                                  一个常见的误解是 JMS 等标准化消息传递 API 可以解决这个问题;它不是。JMS 使两个兼容的消息传递系统对于客户端应用程序来说看起来相同,但它不会使这两个消息传递系统相互协作。为了使消息传递系统能够协同工作,它们需要具有互操作性,这意味着它们使用相同的消息格式并以相同的方式将消息从一个消息存储传输到下一个消息存储。来自两个不同供应商的消息系统很少可以互操作;来自一个供应商的消息存储只能与来自同一供应商的其他消息存储一起使用。

                                                                                                                  A common mis­con­cep­tion is that a stand­ard­ized mes­saging API such as JMS solves this prob­lem; it does not. JMS makes two com­pli­ant mes­saging sys­tems look the same to a client ap­plic­a­tion, but it does noth­ing to make the two mes­saging sys­tems work with each other. For the mes­saging sys­tems to work to­gether, they need to be in­ter­op­er­able, mean­ing that they use the same mes­sage format and trans­mit a mes­sage from one mes­sage store to the next in the same way. Mes­saging sys­tems from two dif­fer­ent vendors are rarely in­ter­op­er­able; a mes­sage store from one vendor can work only with other mes­sage stores from the same vendor.

                                                                                                                  企业中的每个应用程序都可以选择为企业中的每个消息传递系统实现一个客户端,但这会增加消息传递层的复杂性和重复性。如果企业添加了另一个消息系统并且所有应用程序都必须修改,那么这种冗余将变得尤其明显。另一方面,每个应用程序可以选择仅与一个消息传递系统交互并忽略其他消息传递系统上的数据。这将使应用程序更简单,但可能会导致它忽略大量企业数据。所需要的是一种使一个消息传送系统上的另一消息传送系统上的应用程序感兴趣的消息也可以在第二消息传送系统上可用的方式。

                                                                                                                  Each ap­plic­a­tion in the en­ter­prise could choose to im­ple­ment a client for each mes­saging system in the en­ter­prise, but that would in­crease com­plex­ity and du­plic­a­tion in the mes­saging layer. This re­dund­ancy would become es­pe­cially ap­par­ent if the en­ter­prise added yet an­other mes­saging system and all of the ap­plic­a­tions had to be mod­i­fied. On the other hand, each ap­plic­a­tion could choose to in­ter­face with only one mes­saging system and ignore data on the other mes­saging sys­tems. This would make the ap­plic­a­tion sim­pler but could cause it to ignore a great deal of en­ter­prise data. What is needed is a way for mes­sages on one mes­saging system that are of in­terest to ap­plic­a­tions on an­other mes­saging system to be made avail­able on the second mes­saging system as well.

                                                                                                                  使用消息传递桥,消息传递系统之间的连接,用于在系统之间复制消息。

                                                                                                                  Use a Mes­saging Bridge, a con­nec­tion between mes­saging sys­tems that rep­lic­ates mes­sages between sys­tems.

                                                                                                                  图形/04inf11.gif



                                                                                                                  通常,没有实际的方法来连接两个完整的消息系统,因此我们在消息系统之间连接单独的、相应的通道。消息传递桥是一组通道适配器,其中非消息传递客户端实际上是另一个消息传递系统,每对适配器连接一对相应的通道。桥充当从一组通道到另一组通道的映射,并将一个系统的消息格式转换为另一个系统。连接的通道可用于在消息传递系统的传统客户端之间传输消息,或者严格用于发送给其他消息传递系统的消息。

                                                                                                                  Typ­ic­ally, there is no prac­tical way to con­nect two com­plete mes­saging sys­tems, so in­stead we con­nect in­di­vidual, cor­res­pond­ing chan­nels between the mes­saging sys­tems. The Mes­saging Bridge is a set of Chan­nel Ad­apters, where the non-mes­saging client is ac­tu­ally an­other mes­saging system and where each pair of ad­apters con­nects a pair of cor­res­pond­ing chan­nels. The bridge acts as a map from one set of chan­nels to the other and trans­forms the mes­sage format of one system to the other. The con­nec­ted chan­nels may be used to trans­mit mes­sages between tra­di­tional cli­ents of the mes­saging system or strictly for mes­sages in­ten­ded for other mes­saging sys­tems.

                                                                                                                  您可能需要为您的企业自行实施消息传递桥。该桥是一个专门的消息端点应用程序,它是两个消息系统的客户端。当消息在一个消息传递系统中的感兴趣的通道上传递时,桥接器会使用该消息并在另一个消息传递系统中的相应通道上发送具有相同内容的另一条消息。

                                                                                                                  You may need to im­ple­ment the Mes­saging Bridge your­self for your en­ter­prise. The bridge is a spe­cial­ized Mes­sage En­d­point ap­plic­a­tion that is a client of both mes­saging sys­tems. When a mes­sage is de­livered on a chan­nel of in­terest in one mes­saging system, the bridge con­sumes the mes­sage and sends an­other with the same con­tents on the cor­res­pond­ing chan­nel in the other mes­saging system.

                                                                                                                  许多消息传递系统供应商都有产品扩展,用于桥接其他供应商的消息传递系统。因此,您也许可以购买解决方案,而不是自己构建。

                                                                                                                  Many mes­saging system vendors have product ex­ten­sions for bridging to mes­saging sys­tems from other vendors. Thus, you may be able to buy a solu­tion rather than build it your­self.

                                                                                                                  如果其他“消息系统”确实是一个更简单的协议,例如 HTTP,则应用通道适配器模式。

                                                                                                                  If the other "mes­saging system" is really a sim­pler pro­tocol, such as HTTP, apply the Chan­nel Ad­apter pat­tern.

                                                                                                                  消息传递桥是必要的,因为不同的消息传递系统实现对于如何表示消息以及如何将消息从一个存储转发到下一个存储有自己的专有方法。Web 服务可能会对此进行标准化,以便安装的两个消息系统(即使来自不同的供应商)也可以通过使用 Web 服务标准传输消息来充当一个系统。请参阅第 14 章“结束语”中对 WS-Reliability 和 WS-ReliableMessaging 的讨论。

                                                                                                                  Mes­saging Bridge is ne­ces­sary be­cause dif­fer­ent mes­saging system im­ple­ment­a­tions have their own pro­pri­et­ary ap­proaches for how to rep­res­ent mes­sages and how to for­ward them from one store to the next. Web ser­vices may be stand­ard­iz­ing this, such that two mes­saging system in­stalls, even from dif­fer­ent vendors, may be able to act as one by trans­fer­ring mes­saging using Web ser­vices stand­ards. See the dis­cus­sion of WS-Re­li­ab­il­ity and WS-Re­li­ableMes­saging in Chapter 14, "Con­clud­ing Re­marks."

                                                                                                                  示例: 股票交易

                                                                                                                  Ex­ample: Stock Trad­ing

                                                                                                                  经纪公司可能有一个消息系统,其各个办事处的应用程序使用该系统进行通信。银行可能有不同的消息系统,其各个分支机构的应用程序使用该系统进行通信。如果经纪公司和银行决定合并为一家提供银行账户和投资服务的公司,合并后的公司应使用哪种消息系统?该公司可以使用消息桥来连接两个消息系统,而不是重新设计公司一半的应用程序以使用新的消息系统。这样,例如,银行应用程序和经纪应用程序可以协调在储蓄帐户和证券交易帐户之间转移资金。

                                                                                                                  A broker­age house may have one mes­saging system that the ap­plic­a­tions in its vari­ous of­fices use to com­mu­nic­ate. A bank may have a dif­fer­ent mes­saging system that the ap­plic­a­tions in its vari­ous branches use to com­mu­nic­ate. If the broker­age and the bank decide to merge into a single com­pany that offers bank ac­counts and in­vest­ment ser­vices, which mes­saging system should the com­bined com­pany use? Rather than re­design­ing half of the com­pany's ap­plic­a­tions to use the new mes­saging system, the com­pany can use a Mes­saging Bridge to con­nect the two mes­saging sys­tems. This way, for ex­ample, a bank­ing ap­plic­a­tion and a broker­age ap­plic­a­tion can co­ordin­ate to trans­fer money between a sav­ings ac­count and a se­cur­it­ies trad­ing ac­count.



                                                                                                                  示例: MSMQ 桥

                                                                                                                  Ex­ample: MSMQ Bridges

                                                                                                                  MSMQ 定义了一种基于连接器服务器的体系结构,该体系结构使连接器应用程序能够使用其他(非 MSMQ)消息传递系统发送和接收消息。使用连接器服务器的 MSMQ 应用程序可以在来自其他消息传递系统的通道上执行与在 MSMQ 通道上执行的操作相同的操作 [ Dickman]

                                                                                                                  MSMQ defines an ar­chi­tec­ture based on con­nector serv­ers that en­ables con­nector ap­plic­a­tions to send and re­ceive mes­sages using other (non-MSMQ) mes­saging sys­tems. An MSMQ ap­plic­a­tion using a con­nector server can per­form the same op­er­a­tions on chan­nels from other mes­saging sys­tems that it can per­form on MSMQ chan­nels [Dick­man].

                                                                                                                  Microsoft 的主机集成服务器产品包含一个MSMQ-MQSeries 桥接服务,使两个消息系统能够协同工作。它允许 MSMQ 应用程序通过 MQSeries 通道发送消息,反之亦然,从而使两个消息系统充当一个整体。

                                                                                                                  Mi­crosoft's Host In­teg­ra­tion Server product con­tains an MSMQ-MQSer­ies Bridge ser­vice that makes the two mes­saging sys­tems work to­gether. It lets MSMQ ap­plic­a­tions send mes­sages via MQSer­ies chan­nels and vice versa, making the two mes­saging sys­tems act as one.

                                                                                                                  MSMQ-MQSeries Bridge 的许可方 Envoy Technologies 也有一款名为 Envoy Connect 的相关产品。它将 MSMQ 和 BizTalk 服务器与运行在非 Windows 平台(尤其是 J2EE 平台)上的消息传递服务器连接起来,协调企业内的 J2EE 和 .NET 消息传递。

                                                                                                                  Envoy Tech­no­lo­gies, li­censer of the MSMQ-MQSer­ies Bridge, also has a re­lated product called Envoy Con­nect. It con­nects MSMQ and BizTalk serv­ers with mes­saging serv­ers run­ning on non-Win­dows plat­forms, es­pe­cially the J2EE plat­form, co­ordin­at­ing J2EE and .NET mes­saging within an en­ter­prise.



                                                                                                                  示例: SonicMQ 桥

                                                                                                                  Ex­ample: Son­icMQ Bridges

                                                                                                                  Sonic Software 的 SonicMQ 拥有支持 IBM MQSeries、TIBCO TIB/Rendezvous 和 JMS 的 SonicMQ Bridge 产品。这使得 Sonic 通道上的消息也可以在其他消息系统的通道上传输。

                                                                                                                  Sonic Soft­ware's Son­icMQ has Son­icMQ Bridge products that sup­port IBM MQSer­ies, TIBCO TIB/Ren­dez­vous, and JMS. This en­ables mes­sages on Sonic chan­nels to be trans­mit­ted on other mes­saging sys­tems' chan­nels as well.



                                                                                                                    消息总线

                                                                                                                    Message Bus

                                                                                                                    图形/messagebus_icon.gif

                                                                                                                    企业包含多个现有系统,这些系统必须能够共享数据并以统一的方式运行,以响应一组常见的业务请求。

                                                                                                                    An en­ter­prise con­tains sev­eral ex­ist­ing sys­tems that must be able to share data and op­er­ate in a uni­fied manner in re­sponse to a set of common busi­ness re­quests.

                                                                                                                    什么架构能够使单独的应用程序以解耦的方式协同工作,以便可以轻松添加或删除应用程序而不影响其他应用程序?

                                                                                                                    What ar­chi­tec­ture en­ables sep­ar­ate ap­plic­a­tions to work to­gether but in a de­coupled fash­ion such that ap­plic­a­tions can be easily added or re­moved without af­fect­ing the others?



                                                                                                                    一个企业往往包含多种独立运行的应用程序,但又必须以统一的方式协同工作。企业应用程序集成 (EAI) 定义了此问题的解决方案,但没有描述如何实现它。

                                                                                                                    An en­ter­prise often con­tains a vari­ety of ap­plic­a­tions that op­er­ate in­de­pend­ently but must work to­gether in a uni­fied manner. En­ter­prise Ap­plic­a­tion In­teg­ra­tion (EAI) defines a solu­tion to this prob­lem but doesn't de­scribe how to ac­com­plish it.

                                                                                                                    例如,考虑一家销售不同种类保险产品(人寿、健康、汽车、房屋等)的保险公司。由于企业合并和 IT 开发中不断变化的变革趋势,企业由许多独立的应用程序组成,用于管理公司的各种产品。试图向客户销售多种不同类型保单的保险代理人必须为每项保单登录到单独的系统,这不仅浪费精力,而且增加了出错的机会。

                                                                                                                    For ex­ample, con­sider an in­sur­ance com­pany that sells dif­fer­ent kinds of in­sur­ance products (life, health, auto, home, etc.). As a result of cor­por­ate mer­gers and of the vary­ing winds of change in IT de­vel­op­ment, the en­ter­prise con­sists of a number of sep­ar­ate ap­plic­a­tions for man­aging the com­pany's vari­ous products. An in­sur­ance agent trying to sell a cus­tomer sev­eral dif­fer­ent types of policies must log onto a sep­ar­ate system for each policy, wast­ing effort and in­creas­ing the op­por­tun­ity for mis­takes.

                                                                                                                    保险公司 EAI 场景

                                                                                                                    In­sur­ance Com­pany EAI Scen­ario

                                                                                                                    图形/04inf12.gif

                                                                                                                    代理需要一个统一的应用程序来向客户销售保单组合。其他类型的保险公司员工,例如理赔员和客户服务代表,需要自己的应用程序来使用保险产品,但他们也希望自己的应用程序能够呈现统一的视图。各个产品应用程序必须能够协同工作,也许可以在购买多份保单时提供折扣,并处理多份保单涵盖的索赔。

                                                                                                                    The agent needs a single, uni­fied ap­plic­a­tion for selling cus­tom­ers a port­fo­lio of policies. Other types of in­sur­ance com­pany em­ploy­ees, such as claims ad­justers and cus­tomer ser­vice rep­res­ent­at­ives, need their own ap­plic­a­tions for work­ing with the in­sur­ance products, but they also want their ap­plic­a­tions to present a uni­fied view. The in­di­vidual product ap­plic­a­tions must be able to work to­gether, per­haps to offer a dis­count when pur­chas­ing more than one policy and to pro­cess a claim that is covered by more than one policy.

                                                                                                                    IT 部门可以重写产品应用程序,使其全部使用相同的技术并协同工作,但替换已经工作的系统(即使它们不能协同工作)所花费的时间和金钱却令人望而却步。IT 可以为代理创建统一的应用程序,但该应用程序需要连接到实际管理策略的系统。这一新应用程序并没有统一系统,而是创建了一个不与其他系统集成的系统。

                                                                                                                    The IT de­part­ment could re­write the product ap­plic­a­tions to all use the same tech­no­logy and work to­gether, but the amount of time and money to re­place sys­tems that already work (even though they don't work to­gether) is pro­hib­it­ive. IT could create a uni­fied ap­plic­a­tion for the agents, but this ap­plic­a­tion needs to con­nect to the sys­tems that ac­tu­ally manage the policies. Rather than uni­fy­ing the sys­tems, this new ap­plic­a­tion cre­ates one more system that doesn't in­teg­rate with the others.

                                                                                                                    代理应用程序可以与所有这些其他系统集成,但这会使其变得更加复杂。理赔员和客户服务代表的申请也会变得更加复杂。此外,这些统一的用户应用程序不利于产品应用程序之间的相互集成。

                                                                                                                    The agent ap­plic­a­tion could in­teg­rate with all of these other sys­tems, but that would make it much more com­plex. The com­plex­ity would be du­plic­ated in the ap­plic­a­tions for claims ad­justers and cus­tomer ser­vice rep­res­ent­at­ives. Fur­ther­more, these uni­fied user ap­plic­a­tions would not help the product ap­plic­a­tions in­teg­rate with each other.

                                                                                                                    即使所有这些应用程序都可以协同工作,对企业配置的任何更改都可能使其全部停止工作。并非所有应用程序都始终可用,但正在运行的应用程序必须能够继续运行,并将未运行的应用程序的影响降至最低。随着时间的推移,需要在企业中添加和删除应用程序,同时对其他应用程序的影响最小。我们需要的是一种集成架构,使产品应用程序能够以松散耦合的方式进行协调,并使用户应用程序能够与它们集成。

                                                                                                                    Even if all of these ap­plic­a­tions could be made to work to­gether, any change to the en­ter­prise's con­fig­ur­a­tion could make it all stop work­ing. Not all ap­plic­a­tions will be avail­able all of the time, yet the ones that are run­ning must be able to con­tinue with min­imal impact from those that are not run­ning. Over time, ap­plic­a­tions will need to be added to and re­moved from the en­ter­prise, with min­imal impact on the other ap­plic­a­tions. What is needed is an in­teg­ra­tion ar­chi­tec­ture that en­ables the product ap­plic­a­tions to co­ordin­ate in a loosely coupled way and for user ap­plic­a­tions to be able to in­teg­rate with them.

                                                                                                                    将这些应用程序之间的连接中间件构建为消息总线,使它们能够使用消息传递来协同工作。

                                                                                                                    Struc­ture the con­nect­ing mid­dle­ware between these ap­plic­a­tions as a Mes­sage Bus that en­ables them to work to­gether using mes­saging.

                                                                                                                    图形/04inf13.gif



                                                                                                                    消息总线是规范数据模型、通用命令集和消息传递基础设施的组合,允许不同的系统通过一组共享接口进行通信。这类似于计算机系统中的通信总线,它充当 CPU、主存储器和外设之间通信的焦点。正如硬件类比一样,有许多部分组合在一起形成消息总线。

                                                                                                                    A Mes­sage Bus is a com­bin­a­tion of a Ca­non­ical Data Model, a common com­mand set, and a mes­saging in­fra­struc­ture to allow dif­fer­ent sys­tems to com­mu­nic­ate through a shared set of in­ter­faces. This is ana­log­ous to a com­mu­nic­a­tions bus in a com­puter system, which serves as the focal point for com­mu­nic­a­tion between the CPU, main memory, and peri­pher­als. Just as in the hard­ware ana­logy, there are a number of pieces that come to­gether to form the mes­sage bus.

                                                                                                                    1. 通用通信基础设施 正如PCI 总线的物理引脚和电线为 PC 提供了通用的、众所周知的物理基础设施一样,通用基础设施也必须在消息总线中服务于相同的目的。通常,选择消息系统作为物理通信基础设施,在应用程序之间提供跨平台、跨语言的通用适配器。该基础设施可以包括消息路由器功能,以促进消息在系统之间的正确路由。另一个常见的选项是使用发布-订阅通道来促进向所有接收者发送消息。

                                                                                                                    2. Common com­mu­nic­a­tion in­fra­struc­ture Just as the phys­ical pins and wires of a PCI bus provide a common, well-known phys­ical in­fra­struc­ture for a PC, a common in­fra­struc­ture must serve the same pur­pose in a mes­sage bus. Typ­ic­ally, a mes­saging system is chosen to serve as the phys­ical com­mu­nic­a­tions in­fra­struc­ture, provid­ing a cross­plat­form, cross-lan­guage uni­ver­sal ad­apter between the ap­plic­a­tions. The in­fra­struc­ture may in­clude Mes­sage Router cap­ab­il­it­ies to fa­cil­it­ate the cor­rect rout­ing of mes­sages from system to system. An­other common option is to use Pub­lish-Sub­scribe Chan­nels to fa­cil­it­ate send­ing mes­sages to all re­ceiv­ers.

                                                                                                                    3. 适配器不同的系统必须找到一种与消息总线接口的方法。某些应用程序可能已准备好连接到总线,但大多数应用程序需要适配器才能连接到消息传递系统。这些适配器通常是商业或定制的通道适配器服务激活器。它们可能专门用于处理诸如使用适当的参数调用 CICS 事务或将总线的通用数据结构转换为应用程序使用的特定表示形式等任务。这还需要所有系统都同意的规范数据模型。

                                                                                                                    4. Ad­apters The dif­fer­ent sys­tems must find a way to in­ter­face with the Mes­sage Bus. Some ap­plic­a­tions may be ready-built to con­nect to the bus, but most will need ad­apters to con­nect to the mes­saging system. These ad­apters are com­monly com­mer­cial or custom Chan­nel Ad­apters and Ser­vice Ac­tiv­at­ors. They may be spe­cial­ized to handle tasks like in­vok­ing CICS trans­ac­tions with the proper para­met­ers or con­vert­ing the bus's gen­eral data struc­tures to the spe­cific rep­res­ent­a­tion an ap­plic­a­tion uses. This also re­quires a Ca­non­ical Data Model that all sys­tems can agree on.

                                                                                                                    5. 通用命令结构 正如PC 体系结构具有一组通用命令来表示物理总线上可能的不同操作(从地址读取字节、向地址写入字节)一样,消息总线中的所有参与者也必须有通用命令可以理解。命令消息说明了此功能的工作原理。另一个常见的实现是数据类型 Channel ,其中消息路由器明确决定如何将特定消息(如采购订单)路由到特定端点。最后,这个类比就不成立了,因为总线上承载的消息的级别比物理总线上承载的“读/写”类型的消息要细粒度得多。

                                                                                                                    6. Common com­mand struc­ture Just as PC ar­chi­tec­tures have a common set of com­mands to rep­res­ent the dif­fer­ent op­er­a­tions pos­sible on the phys­ical bus (read bytes from an ad­dress, write bytes to an ad­dress), there must be common com­mands that all the par­ti­cipants in the Mes­sage Bus can un­der­stand. Com­mand Mes­sage il­lus­trates how this fea­ture works. An­other common im­ple­ment­a­tion for this is the Data­type Chan­nel, where a Mes­sage Router makes an ex­pli­cit de­cision as to how to route par­tic­u­lar mes­sages (like pur­chase orders) to par­tic­u­lar en­d­points. It is at the end that the ana­logy breaks down, since the level of the mes­sages car­ried on the bus are much more fine-grained than the "read/write" kinds of mes­sages car­ried on a phys­ical bus.

                                                                                                                    在我们的 EAI 示例中,消息总线可以充当各种保险系统之间的通用连接器,以及希望连接到保险系统的客户端应用程序的通用接口。

                                                                                                                    In our EAI ex­ample, a Mes­sage Bus could serve as a uni­ver­sal con­nector between the vari­ous in­sur­ance sys­tems and as a uni­ver­sal in­ter­face for client ap­plic­a­tions that wish to con­nect to the in­sur­ance sys­tems.

                                                                                                                    保险公司消息总线

                                                                                                                    In­sur­ance Com­pany Mes­sage Bus

                                                                                                                    图形/04inf14.gif

                                                                                                                    这里我们有两个只了解消息总线的 GUI ;他们完全不知道底层系统的复杂性。总线负责将命令消息路由到适当的底层系统。在某些情况下,处理命令消息的最佳方法是构建一个系统适配器,用于解释命令,然后以系统理解的方式与系统进行通信(例如,调用 CICS 事务,或调用 C++ API) 。在其他情况下,可以将命令处理逻辑直接构建到现有系统中,作为调用当前逻辑的附加方式。

                                                                                                                    Here we have two GUIs that know only about the Mes­sage Bus; they are en­tirely un­aware of the com­plex­it­ies of the un­der­ly­ing sys­tems. The bus is re­spons­ible for rout­ing Com­mand Mes­sages to the proper un­der­ly­ing sys­tems. In some cases, the best way to handle the com­mand mes­sages is to build an ad­apter to the system that in­ter­prets the com­mand and then com­mu­nic­ates with the system in a way it un­der­stands (in­vok­ing a CICS trans­ac­tion, for in­stance, or call­ing a C++ API). In other cases, it may be pos­sible to build the com­mand-pro­cess­ing logic dir­ectly into the ex­ist­ing system as an ad­di­tional way to invoke cur­rent logic.

                                                                                                                    一旦为代理 GUI 开发了消息总线,就可以轻松地为其他 GUI 重用,例如索赔处理者、客户服务代表以及使用 Web 界面浏览自己帐户的客户的 GUI。这些 GUI 应用程序的功能和安全控制各不相同,但它们与后端应用程序配合使用的需求是相同的。

                                                                                                                    Once the Mes­sage Bus has been de­ve­loped for the agent GUI, it is easy to reuse for other GUIs, such as those for claims pro­cessors, cus­tomer ser­vice rep­res­ent­at­ives, and cus­tom­ers who use a Web in­ter­face for to browse their own ac­counts. The fea­tures and se­cur­ity con­trol of these GUI ap­plic­a­tions differ, but their need to work with the back-end ap­plic­a­tions is the same.

                                                                                                                    消息总线为企业形成了一个简单、有用的面向服务的体系结构。每个服务至少有一个接受商定格式请求的请求通道,并且可能有一个支持指定回复格式的相应回复通道。任何参与者应用程序都可以通过发出请求并等待回复来使用这些服务。实际上,请求通道充当可用服务的目录。

                                                                                                                    A Mes­sage Bus forms a simple, useful ser­vice-ori­ented ar­chi­tec­ture for an en­ter­prise. Each ser­vice has at least one re­quest chan­nel that ac­cepts re­quests of an agreed-upon format and prob­ably a cor­res­pond­ing reply chan­nel that sup­ports a spe­cified reply format. Any par­ti­cipant ap­plic­a­tion can use these ser­vices by making re­quests and wait­ing for replies. The re­quest chan­nels, in effect, act as a dir­ect­ory of the ser­vices avail­able.

                                                                                                                    消息总线要求所有使用该总线的应用程序都使用相同的规范数据模型。将消息添加到总线的应用程序可能需要依赖消息路由器将消息路由到适当的最终目的地。未设计为与消息传递系统交互的应用程序可能需要通道适配器服务激活器

                                                                                                                    A Mes­sage Bus re­quires that all of the ap­plic­a­tions using the bus use the same Ca­non­ical Data Model. Ap­plic­a­tions adding mes­sages to the bus may need to depend on Mes­sage Routers to route the mes­sages to the ap­pro­pri­ate final des­tin­a­tions. Ap­plic­a­tions not de­signed to in­ter­face with a mes­saging system may re­quire Chan­nel Ad­apters and Ser­vice Ac­tiv­at­ors.

                                                                                                                    示例: 股票交易

                                                                                                                    Ex­ample: Stock Trad­ing

                                                                                                                    股票交易系统可能希望提供一套统一的服务,包括股票交易、债券拍卖、报价、投资组合管理等。这可能需要几个必须相互协调的独立后端系统。为了统一前端客户 GUI 的服务,系统可以采用中间应用程序来提供所有这些服务并将其性能委托给后端系统。后端系统甚至可以通过这个中间应用程序进行协调。然而,中间应用程序往往会成为瓶颈和单点故障。

                                                                                                                    A stock trad­ing system may wish to offer a uni­fied suite of ser­vices in­clud­ing stock trades, bond auc­tions, price quotes, port­fo­lio man­age­ment, and so on. This may re­quire sev­eral sep­ar­ate back-end sys­tems that have to co­ordin­ate with each other. To unify the ser­vices for a front-end cus­tomer GUI, the system could employ an in­ter­me­di­ate ap­plic­a­tion that offered all of these ser­vices and del­eg­ated their per­form­ance to the back-end sys­tems. The back-end sys­tems could even co­ordin­ate through this in­ter­me­di­ary ap­plic­a­tion. How­ever, the in­ter­me­di­ary ap­plic­a­tion would tend to become a bot­tle­neck and a single point of fail­ure.

                                                                                                                    更好的方法可能是消息总线,它具有用于请求各种服务并获取响应的通道,而不是中间应用程序。该总线还可以使后端系统能够相互协调。前端系统可以简单地连接到总线并使用它来调用服务。总线可以相对容易地分布在多台计算机上,以提供负载分布和容错能力。

                                                                                                                    Rather than an in­ter­me­di­ary ap­plic­a­tion, a better ap­proach might be a Mes­sage Bus with chan­nels for re­quest­ing vari­ous ser­vices and get­ting re­sponses. This bus could also enable back-end sys­tems to co­ordin­ate with each other. A front-end system could simply con­nect to the bus and use it to invoke ser­vices. The bus could be dis­trib­uted re­l­at­ively easily across mul­tiple com­puters to provide load dis­tri­bu­tion and fault tol­er­ance.

                                                                                                                    一旦消息总线到位,连接前端 GUI 就会很容易;他们每个人只需要从适当的渠道发送和接收消息。一个 GUI 可以让零售经纪商管理其客户的投资组合。另一个基于 Web 的 GUI 可以让任何拥有 Web 浏览器的客户管理自己的产品组合。另一个非 GUI 前端可能支持个人财务程序,如 Intuit 的 Quicken 和 Microsoft 的 Money,使客户能够使用这些程序下载交易和当前价格。一旦消息总线就位,开发新的用户应用程序就会变得更加简单。

                                                                                                                    Once the Mes­sage Bus is in place, con­nect­ing front-end GUIs would be easy; they each just need to send and re­ceive mes­sages from the proper chan­nels. One GUI might enable a retail broker to manage his cus­tom­ers' port­fo­lios. An­other Web-based GUI could enable any cus­tomer with a Web browser to manage his own port­fo­lio. An­other non-GUI front end might sup­port per­sonal fin­ance pro­grams like Intuit's Quicken and Mi­crosoft's Money, en­abling cus­tom­ers using those pro­grams to down­load trades and cur­rent prices. Once the Mes­sage Bus is in place, de­vel­op­ing new user ap­plic­a­tions is much sim­pler.

                                                                                                                    同样,交易系统可能希望利用新的后端应用程序,例如将一个交易应用程序切换为另一个交易应用程序或将报价请求分散到多个应用程序。实现这样的更改就像在消息总线中添加和删除应用程序一样简单。一旦新的应用程序就位,其他应用程序就不必更改;他们只是像往常一样继续在总线频道上发送消息。

                                                                                                                    Like­wise, the trad­ing system may want to take ad­vant­age of new back-end ap­plic­a­tions, such as by switch­ing one trad­ing ap­plic­a­tion for an­other or spread­ing price quote re­quests across mul­tiple ap­plic­a­tions. Im­ple­ment­ing a change like this is as simple as adding and re­mov­ing ap­plic­a­tions from the Mes­sage Bus. Once the new ap­plic­a­tions are in place, none of the other ap­plic­a­tions have to change; they just keep send­ing mes­sages on the bus's chan­nels as usual.



                                                                                                                      介绍

                                                                                                                      Introduction

                                                                                                                      第 3 章“消息系统”中,我们讨论了Message 。当两个应用程序想要交换一段数据时,它们通过将其包装在消息中来实现。虽然消息通道本身无法传输原始数据,但它可以传输封装在消息中的数据。创建和发送消息会引发其他几个问题。

                                                                                                                      In Chapter 3, "Mes­saging Sys­tems," we dis­cussed Mes­sage. When two ap­plic­a­tions want to ex­change a piece of data, they do so by wrap­ping it in a mes­sage. Whereas a Mes­sage Chan­nel cannot trans­mit raw data per se, it can trans­mit the data wrapped in a mes­sage. Cre­at­ing and send­ing a Mes­sage raises sev­eral other issues.

                                                                                                                      消息意图 消息最终只是数据束,但发送者可以对期望接收者对消息执行的操作有不同的意图。它可以发送命令消息,指定发送者希望调用的接收者上的函数或方法。发送者告诉接收者要运行什么代码。它可以发送文档消息,使发送者能够将其数据结构之一传输到接收者。发送者将数据传递给接收者,但没有指定接收者必须用它做什么。或者它可以发送事件消息,通知接收者发送者的变化。发送者并不告诉接收者如何反应,只是提供通知。

                                                                                                                      返回响应 当应用程序发送消息时,它通常期望一个响应来确认该消息已被处理并提供结果。这是一个请求-回复场景。请求通常是命令消息,回复包含结果值或异常的文档消息。请求者应请求中指定返回地址,以告诉回复者使用哪个通道来传输回复。请求者可能有多个请求正在处理中,因此回复应包含一个相关标识符,指定此回复对应于哪个请求。

                                                                                                                      有两种常见的请求-回复场景值得注意;两者都涉及命令消息请求和相应的文档消息回复。在第一个场景(消息 RPC)中,请求者不仅想要调用回复者上的函数,而且还想要函数的返回值。这就是应用程序使用消息传递执行 RPC(远程过程调用)的方式。 另一种场景Messaging Query,请求者执行查询;回复者执行查询并在回复中返回结果。这就是应用程序如何使用消息传递来远程执行查询。

                                                                                                                      海量数据 有时,应用程序想要传输非常大的数据结构,该数据结构可能无法轻松地容纳在单个消息中。在这种情况下,将数据分成更易于管理的块并将它们作为消息序列发送。块必须作为序列发送,而不仅仅是一堆消息,以便接收者可以重建原始数据结构。

                                                                                                                      消息缓慢 消息传递的一个问题是发送者通常不知道接收者需要多长时间才能收到消息。然而,消息内容可能是时间敏感的,因此如果在特定期限内未收到消息,则应忽略并丢弃该消息。在这种情况下,发送者可以使用消息过期来指定过期日期。如果消息传递系统无法在过期前传递消息,则应丢弃该消息或将其移至死信通道。同样,如果接收方在过期后收到消息,则应丢弃该消息。

                                                                                                                      Mes­sage intent Mes­sages are ul­ti­mately just bundles of data, but the sender can have dif­fer­ent in­ten­tions for what it ex­pects the re­ceiver to do with the mes­sage. It can send a Com­mand Mes­sage, spe­cify­ing a func­tion or method on the re­ceiver that the sender wishes to invoke. The sender is telling the re­ceiver what code to run. It can send a Doc­u­ment Mes­sage, en­abling the sender to trans­mit one of its data struc­tures to the re­ceiver. The sender is passing the data to the re­ceiver but not spe­cify­ing what the re­ceiver should ne­ces­sar­ily do with it. Or it can send an Event Mes­sage, no­ti­fy­ing the re­ceiver of a change in the sender. The sender is not telling the re­ceiver how to react, just provid­ing no­ti­fic­a­tion.

                                                                                                                      Re­turn­ing a re­sponse When an ap­plic­a­tion sends a mes­sage, it often ex­pects a re­sponse con­firm­ing that the mes­sage has been pro­cessed and provid­ing the result. This is a Re­quest-Reply scen­ario. The re­quest is usu­ally a Com­mand Mes­sage, and the reply is a Doc­u­ment Mes­sage con­tain­ing a result value or an ex­cep­tion. The re­questor should spe­cify a Return Ad­dress in the re­quest to tell the replier what chan­nel to use to trans­mit the reply. The re­questor may have mul­tiple re­quests in pro­cess, so the reply should con­tain a Cor­rel­a­tion Iden­ti­fier that spe­cifies which re­quest this reply cor­res­ponds to.

                                                                                                                      There are two common Re­quest-Reply scen­arios worth noting; both in­volve a Com­mand Mes­sage re­quest and a cor­res­pond­ing Doc­u­ment Mes­sage reply. In the first scen­ario, Mes­saging RPC, the re­questor not only wants to invoke a func­tion on the replier, but also wants the return value from the func­tion. This is how ap­plic­a­tions per­form an RPC (Remote Pro­ced­ure Call) using Mes­saging. In the other scen­ario, Mes­saging Query, the re­questor per­forms a query; the replier ex­ecutes the query and re­turns the res­ults in the reply. This is how ap­plic­a­tions use mes­saging to per­form a query re­motely.

                                                                                                                      Huge amounts of data Some­times ap­plic­a­tions want to trans­fer a really large data struc­ture, one that may not fit com­fort­ably in a single mes­sage. In this case, break the data into more man­age­able chunks and send them as a Mes­sage Se­quence. The chunks must be sent as a se­quence, not just a bunch of mes­sages, so the re­ceiver can re­con­struct the ori­ginal data struc­ture.

                                                                                                                      Slow mes­sages A con­cern with mes­saging is that the sender often does not know how long it will take for the re­ceiver to re­ceive the mes­sage. Yet, the mes­sage con­tents may be time-sens­it­ive, so if the mes­sage isn't re­ceived by a cer­tain dead­line, it should be ig­nored and dis­carded. In this situ­ation, the sender can use Mes­sage Ex­pir­a­tion to spe­cify an ex­pir­a­tion date. If the mes­saging system cannot de­liver a mes­sage by its ex­pir­a­tion, it should dis­card the mes­sage or move it to a Dead Letter Chan­nel. Like­wise, if a re­ceiver gets a mes­sage after its ex­pir­a­tion, it should dis­card the mes­sage.

                                                                                                                      总之,仅仅选择使用消息是不够的。当必须传输数据时,必须通过 Message 来完成。 本章解释了使消息发挥作用的其他决策。

                                                                                                                      In sum­mary, simply choos­ing to use a Mes­sage is in­suf­fi­cient. When data must be trans­ferred, it must be done through a Mes­sage. This chapter ex­plains other de­cisions that are part of making mes­sages work.

                                                                                                                        命令信息

                                                                                                                        Command Message

                                                                                                                        图形/commandmessage_icon.gif

                                                                                                                        应用程序需要调用其他应用程序提供的功能。它通常会使用远程过程调用,但它希望利用使用消息传递

                                                                                                                        An ap­plic­a­tion needs to invoke func­tion­al­ity provided by other ap­plic­a­tions. It would typ­ic­ally use Remote Pro­ced­ure In­voc­a­tion, but it would like to take ad­vant­age of the be­ne­fits of using Mes­saging.

                                                                                                                        如何使用消息传递来调用另一个应用程序中的过程?

                                                                                                                        How can mes­saging be used to invoke a pro­ced­ure in an­other ap­plic­a­tion?



                                                                                                                        远程过程调用的优点是它是同步的,因此当调用者的线程阻塞时,调用会立即执行。但这也是一个缺点。如果由于网络故障或远程进程未运行和侦听而无法立即执行调用,则调用不起作用。如果调用是异步的,它可以继续尝试,直到成功调用远程应用程序中的过程。

                                                                                                                        The ad­vant­age of Remote Pro­ced­ure In­voc­a­tion is that it's syn­chron­ous, so the call is per­formed im­me­di­ately while the caller's thread blocks. But that's also a dis­ad­vant­age. If the call cannot be ex­ecuted im­me­di­atelyeither be­cause the net­work is down or be­cause the remote pro­cess isn't run­ning and listen­in­gthen the call doesn't work. If the call were asyn­chron­ous, it could keep trying until the pro­ced­ure in the remote ap­plic­a­tion is suc­cess­fully in­voked.

                                                                                                                        本地调用比远程调用更可靠。如果调用者可以将过程的调用作为 Message 传输到接收者则接收者可以在本地执行该调用。所以,问题是如何将过程的调用变成消息。

                                                                                                                        A local in­voc­a­tion is more re­li­able than a remote in­voc­a­tion. If the caller could trans­mit the pro­ced­ure's in­voc­a­tion to the re­ceiver as a Mes­sage, then the re­ceiver could ex­ecute the in­voc­a­tion loc­ally. So, the ques­tion is how to make a pro­ced­ure's in­voc­a­tion into a mes­sage.

                                                                                                                        有一个完善的模式可以将请求封装为对象。命令模式[ GoF]展示了如何将请求转换为可以存储和传递的对象。如果该对象是一条消息,那么它可以存储在 Message Channel 中并通过Message Channel 传递。同样,命令的状态(例如方法参数)可以存储在消息的状态中。

                                                                                                                        There's a well-es­tab­lished pat­tern for en­cap­su­lat­ing a re­quest as an object. The Com­mand pat­tern [GoF] shows how to turn a re­quest into an object that can be stored and passed around. If this object were a mes­sage, then it could be stored in and passed around through a Mes­sage Chan­nel. Like­wise, the com­mand's state (such as method para­met­ers) can be stored in the mes­sage's state.

                                                                                                                        使用命令消息可靠地调用另一个应用程序中的过程。

                                                                                                                        Use a Com­mand Mes­sage to re­li­ably invoke a pro­ced­ure in an­other ap­plic­a­tion.

                                                                                                                        图形/05inf01.gif



                                                                                                                        命令没有特定的消息类型;命令消息只是碰巧包含命令的常规消息。在JMS中,命令消息可以是任何类型的消息;示例包括包含可序列化命令对象ObjectMessage、包含 XML 形式的命令TextMessage 等。在 .NET 中,命令消息是其中存储有命令的消息。简单对象访问协议 (Simple Object Access Protocol, ) 请求是一条命令消息。

                                                                                                                        There is no spe­cific mes­sage type for com­mands; a Com­mand Mes­sage is simply a reg­u­lar mes­sage that hap­pens to con­tain a com­mand. In JMS, the com­mand mes­sage could be any type of mes­sage; ex­amples in­clude an Ob­ject­Mes­sage con­tain­ing a Seri­al­iz­able com­mand object, a Text­Mes­sage con­tain­ing the com­mand in XML form, and so on. In .NET, a com­mand mes­sage is a Mes­sage with a com­mand stored in it. A Simple Object Access Pro­tocol ( ) re­quest is a com­mand mes­sage.

                                                                                                                        命令消息通常通过点对点,以便每个命令仅被使用和调用一次。

                                                                                                                        Com­mand Mes­sages are usu­ally sent over a Point-to-Point Chan­nel so that each com­mand will be con­sumed and in­voked only once.

                                                                                                                        示例: SOAP 和 WSDL

                                                                                                                        Ex­ample: SOAP and WSDL

                                                                                                                        根据 SOAP 协议 [ SOAP 1.1 ] 和 WSDL 服务描述 [ WSDL 1.1 ],当使用 RPC 样式的 SOAP 消息时,请求消息就是此命令消息模式的一个示例。通过这种用法,SOAP 消息正文(XML 文档)包含要在接收器中调用的方法的名称以及要传递到该方法中的参数值。此方法名称必须与接收方 WSDL 中定义的消息名称之一相同。

                                                                                                                        With the SOAP pro­tocol [SOAP 1.1] and WSDL ser­vice de­scrip­tion [WSDL 1.1], when using RPC-style SOAP mes­sages, the re­quest mes­sage is an ex­ample of this Com­mand Mes­sage pat­tern. With this usage, the SOAP mes­sage body (an XML doc­u­ment) con­tains the name of the method to invoke in the re­ceiver and the para­meter values to pass into the method. This method name must be the same as one of the mes­sage names defined in the re­ceiver's WSDL.

                                                                                                                        SOAP 规范中的此示例使用名为 symbol 的单个参数调用接收者的GetLastTradePrice方法。

                                                                                                                        This ex­ample from the SOAP spe­cific­a­tion in­vokes the re­ceiver's Get­LastTrade­Price method with a single para­meter called symbol.

                                                                                                                        <SOAP-ENV:信封
                                                                                                                          xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
                                                                                                                          SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
                                                                                                                           <SOAP-ENV:正文>
                                                                                                                               <m:GetLastTradePrice xmlns:m="Some-URI">
                                                                                                                                   <符号>DIS</符号>
                                                                                                                               </m:获取最后交易价格>
                                                                                                                           </SOAP-ENV:正文>
                                                                                                                        </SOAP-ENV:信封>
                                                                                                                        
                                                                                                                        <SOAP-ENV:En­vel­ope
                                                                                                                          xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/en­vel­ope/"
                                                                                                                          SOAP-ENV:en­cod­ing­Style="http://schemas.xmlsoap.org/soap/en­cod­ing/">
                                                                                                                           <SOAP-ENV:Body>
                                                                                                                               <m:Get­LastTrade­Price xmlns:m="Some-URI">
                                                                                                                                   <symbol>DIS</symbol>
                                                                                                                               </m:Get­LastTrade­Price>
                                                                                                                           </SOAP-ENV:Body>
                                                                                                                        </SOAP-ENV:En­vel­ope>
                                                                                                                        

                                                                                                                        在 SOAP 命令中,人们可能期望方法名称是某个标准<method>元素的值;实际上,方法名称是方法元素的名称,以m命名空间为前缀。为每个方法提供单独的 XML 元素类型可以使 XML 数据的验证更加精确,因为方法元素类型可以指定参数的名称、类型和顺序。

                                                                                                                        In a SOAP com­mand, one might expect the method name to be the value of some stand­ard <method> ele­ment; ac­tu­ally, the method name is the name of the method ele­ment, pre­fixed by the m namespace. Having a sep­ar­ate XML ele­ment type for each method makes val­id­at­ing the XML data much more pre­cise, be­cause the method ele­ment type can spe­cify the para­met­ers' names, types, and order.



                                                                                                                          文档留言

                                                                                                                          Document Message

                                                                                                                          图形/documentmessage_icon.gif

                                                                                                                          一个应用程序想要将数据传输到另一个应用程序。它可以使用文件传输或共享数据库来做到这一点,但这些方法都有缺点。使用消息传递可能会更好地进行传输

                                                                                                                          An ap­plic­a­tion would like to trans­fer data to an­other ap­plic­a­tion. It could do so using File Trans­fer or Shared Data­base, but those ap­proaches have short­com­ings. The trans­fer might work better using Mes­saging.

                                                                                                                          如何使用消息传递在应用程序之间传输数据?

                                                                                                                          How can Mes­saging be used to trans­fer data between ap­plic­a­tions?



                                                                                                                          这是分布式处理中的一个经典问题:一个进程拥有另一个进程需要的数据。文件传输很容易使用,但它不能很好地协调应用程序。一个应用程序写入的文件可能会在另一应用程序读取它之前很长一段时间未使用。如果多个应用程序要读取它,则不清楚谁应该负责删除它。

                                                                                                                          This is a clas­sic prob­lem in dis­trib­uted pro­cess­ing: One pro­cess has data that an­other one needs. File Trans­fer is easy to use, but it doesn't co­ordin­ate ap­plic­a­tions very well. A file writ­ten by one ap­plic­a­tion may sit unused for quite a while before an­other ap­plic­a­tion reads it. If sev­eral ap­plic­a­tions are sup­posed to read it, it'll be un­clear who should take re­spons­ib­il­ity for de­let­ing it.

                                                                                                                          共享数据库需要一旦数据进入数据库,就存在其他不应访问该数据的应用程序现在可以访问该数据的风险。触发数据接收者来读取数据可能很困难,并且协调多个读取器可能会造成谁应该删除数据的混乱。

                                                                                                                          Shared Data­base re­quires adding new schema to the data­base to ac­com­mod­ate the data or force-fit­ting the data into the ex­ist­ing schema. Once the data is in the data­base, there's the risk that other ap­plic­a­tions that should not have access to the data now do. Trig­ger­ing the re­ceiver of the data to come and read it can be dif­fi­cult, and co­ordin­at­ing mul­tiple read­ers can create con­fu­sion about who should delete the data.

                                                                                                                          远程过程调用可用于发送数据,但调用者还通过被调用的过程告诉接收者如何处理数据。同样,命令消息将此外,远程过程调用

                                                                                                                          Remote Pro­ced­ure In­voc­a­tion can be used to send the data, but then the caller is also telling the re­ceiv­er­via the pro­ced­ure being in­voked­what to do with the data. Like­wise, a Com­mand Mes­sage would trans­fer the data but would be overly spe­cific about what the re­ceiver should do with the data. Also, Remote Pro­ced­ure In­voc­a­tion as­sumes two-way com­mu­nic­a­tion, which is un­ne­ces­sary if we only want to pass data from one ap­plic­a­tion to an­other.

                                                                                                                          然而,我们确实想使用消息传递来传输数据。消息传递比 RPC 更可靠。点对点通道可用于确保只有一个接收者获得数据(无重复),或者发布-订阅通道可用于确保任何想要该数据的接收者都能获得数据的副本。因此,诀窍是利用消息传递的优势,但又不能让消息变得太像 RPC。

                                                                                                                          Yet, we do want to use Mes­saging to trans­fer the data. Mes­saging is more re­li­able than an RPC. A Point-to-Point Chan­nel can be used to make sure that only one re­ceiver gets the data (no du­plic­a­tion), or a Pub­lish-Sub­scribe Chan­nel can be used to make sure that any re­ceiver who wants the data gets a copy of it. So, the trick is to take ad­vant­age of Mes­saging without making the Mes­sage too much like an RPC.

                                                                                                                          使用文档消息在应用程序之间可靠地传输数据结构。

                                                                                                                          Use a Doc­u­ment Mes­sage to re­li­ably trans­fer a data struc­ture between ap­plic­a­tions.

                                                                                                                          图形/05inf02.gif



                                                                                                                          命令消息告诉接收者调用某些行为,而文档消息只是传递数据并让接收者决定如何处理数据(如果有的话)。数据是单个数据单元、单个对象或可以分解为更小的单元的数据结构。

                                                                                                                          Whereas a Com­mand Mes­sage tells the re­ceiver to invoke cer­tain be­ha­vior, a Doc­u­ment Mes­sage just passes data and lets the re­ceiver decide what, if any­thing, to do with the data. The data is a single unit of data, a single object or data struc­ture that may de­com­pose into smal­ler units.

                                                                                                                          文档消息看起来非常像事件消息;主要区别在于时间和内容的问题。文档消息的重要部分其内容:文档。成功传输文件很重要;发送和接收的时间不太重要。保证交付可能是一个考虑因素;消息过期可能不是。相反,事件消息的存在和时机通常比其内容更重要。

                                                                                                                          Doc­u­ment Mes­sages can seem very much like Event Mes­sages; the main dif­fer­ence is a matter of timing and con­tent. The im­port­ant part of a Doc­u­ment Mes­sage is its con­tent: the doc­u­ment. Suc­cess­fully trans­fer­ring the doc­u­ment is im­port­ant; the timing of when it is sent and re­ceived is less im­port­ant. Guar­an­teed De­liv­ery may be a con­sid­er­a­tion; Mes­sage Ex­pir­a­tion prob­ably is not. In con­trast, an Event Mes­sages ex­ist­ence and timing are often more im­port­ant than its con­tent.

                                                                                                                          文档消息可以是消息传递系统中的任何类型的消息。在 JMS 中,文档消息可以是包含文档的可序列化数据对象的ObjectMessage ,也可以是包含 XML 形式的数据TextMessage 。在 .NET 中,文档消息是其中存储有数据的消息简单对象访问协议 (SOAP) 回复消息是文档消息。

                                                                                                                          A Doc­u­ment Mes­sage can be any kind of mes­sage in the mes­saging system. In JMS, the doc­u­ment mes­sage may be an Ob­ject­Mes­sage con­tain­ing a Seri­al­iz­able data object for the doc­u­ment, or it may be a Text­Mes­sage con­tain­ing the data in XML form. In .NET, a doc­u­ment mes­sage is a Mes­sage with the data stored in it. A Simple Object Access Pro­tocol (SOAP) reply mes­sage is a doc­u­ment mes­sage.

                                                                                                                          文档消息通常使用点对点以将文档从一个进程移动到另一个进程而不进行复制。消息传递可用于实现简单的工作流程,方法是将文档传递到应用程序,应用程序修改该文档,然后将其传递到另一个应用程序。在某些情况下,可以通过发布-订阅通道广播文档消息但这会创建文档的多个副本。副本必须是只读的;否则,如果接收者更改副本,系统中将出现该文档的多个副本,每个副本包含不同的数据。在Request-Reply中,回复通常是文档消息 ,其中结果值是文档。

                                                                                                                          Doc­u­ment Mes­sages are usu­ally sent using a Point-to-Point Chan­nel to move the doc­u­ment from one pro­cess to an­other without du­plic­at­ing it. Mes­saging can be used to im­ple­ment simple work­flow by passing a doc­u­ment to an ap­plic­a­tion that mod­i­fies the doc­u­ment and then passes it to an­other ap­plic­a­tion. In some cases, a doc­u­ment mes­sage can be broad­cast via a Pub­lish-Sub­scribe Chan­nel, but this cre­ates mul­tiple copies of the doc­u­ment. The copies need to be read-only; oth­er­wise, if the re­ceiv­ers change the copies, there will be mul­tiple copies of the doc­u­ment in the system, each con­tain­ing dif­fer­ent data. In Re­quest-Reply, the reply is usu­ally a Doc­u­ment Mes­sage, where the result value is the doc­u­ment.

                                                                                                                          示例: Java 和 XML

                                                                                                                          Ex­ample: Java and XML

                                                                                                                          以下示例(取自 [ Graham ] 中的示例 XML 模式)显示了如何将简单的采购订单表示为 XML 并使用 JMS 作为消息发送。

                                                                                                                          The fol­low­ing ex­ample (drawn from the ex­ample XML schema in [Graham]) shows how a simple pur­chase order can be rep­res­en­ted as XML and sent as a mes­sage using JMS.

                                                                                                                          
                                                                                                                          Session session = // 获取会话
                                                                                                                          Destination dest = // 获取目的地
                                                                                                                          MessageProducer 发送者 = session.createProducer(dest);
                                                                                                                          字符串购买订单 =
                                                                                                                          " <po id=\"48881\" 提交=\"2002-04-23\">
                                                                                                                                  <收货地址>
                                                                                                                                      <公司>巧克力狂</公司>
                                                                                                                                      <街道>北街2112号</街道>
                                                                                                                                      <城市>卡里</城市>
                                                                                                                                      <州>北卡罗来纳州</州>
                                                                                                                                      <邮政编码>27522</邮政编码>
                                                                                                                                  </发货>
                                                                                                                                  <订单>
                                                                                                                                      <商品 sku=\"22211\" 数量=\"40\">
                                                                                                                                          <描述>兔子,黑巧克力,大号<
                                                                                                                          图形/ccc.gif/描述>
                                                                                                                                      </项目>
                                                                                                                                  </订单>
                                                                                                                              </po>”;
                                                                                                                          TextMessage 消息 = session.createTextMessage();
                                                                                                                          message.setText(采购订单);
                                                                                                                          发送者.发送(消息);
                                                                                                                          
                                                                                                                          
                                                                                                                          Ses­sion ses­sion = // Obtain the ses­sion
                                                                                                                          Des­tin­a­tion dest = // Obtain the des­tin­a­tion
                                                                                                                          Mes­sage­Pro­du­cer sender = ses­sion.cre­ate­Pro­du­cer(dest);
                                                                                                                          String pur­chase­Order =
                                                                                                                          "    <po id=\"48881\" sub­mit­ted=\"2002-04-23\">
                                                                                                                                  <shipTo>
                                                                                                                                      <com­pany>Choco­hol­ics</com­pany>
                                                                                                                                      <street>2112 North Street</street>
                                                                                                                                      <city>Cary</city>
                                                                                                                                      <state>NC</state>
                                                                                                                                      <postalCode>27522</postalCode>
                                                                                                                                  </shipTo>
                                                                                                                                  <order>
                                                                                                                                      <item sku=\"22211\" quant­ity=\"40\">
                                                                                                                                          <de­scrip­tion>Bunny, Dark Chocol­ate, Large<
                                                                                                                          /de­scrip­tion>
                                                                                                                                      </item>
                                                                                                                                  </order>
                                                                                                                              </po>";
                                                                                                                          Text­Mes­sage mes­sage = ses­sion.cre­at­e­Text­Mes­sage();
                                                                                                                          mes­sage.set­Text(pur­chase­Order);
                                                                                                                          sender.send(mes­sage);
                                                                                                                          



                                                                                                                          示例: SOAP 和 WSDL

                                                                                                                          Ex­ample: SOAP and WSDL

                                                                                                                          根据 SOAP 协议 [ SOAP 1.1 ] 和 WSDL 服务描述 [ WSDL 1.1 ],当使用文档样式的 SOAP 消息时,SOAP 消息是文档消息的一个示例。SOAP 消息主体是 XML 文档(或已转换为 XML 文档的某种数据结构),并且 SOAP 消息将该文档从发送者(例如,客户端)传输到接收者(例如,服务器) 。

                                                                                                                          With the SOAP pro­tocol [SOAP 1.1] and WSDL ser­vice de­scrip­tion [WSDL 1.1], when using doc­u­ment-style SOAP mes­sages, the SOAP mes­sage is an ex­ample of a Doc­u­ment Mes­sage. The SOAP mes­sage body is an XML doc­u­ment (or some kind of data struc­ture that has been con­ver­ted into an XML doc­u­ment), and the SOAP mes­sage trans­mits that doc­u­ment from the sender (e.g., the client) to the re­ceiver (e.g., the server).

                                                                                                                          当使用 RPC 样式的 SOAP 消息传递时,响应消息就是此模式的一个示例。通过这种用法,SOAP 消息正文(XML 文档)包含所调用方法的返回值。SOAP 规范中的此示例通过调用GetLastTradePrice方法返回答案。

                                                                                                                          When using RPC-style SOAP mes­saging, the re­sponse mes­sage is an ex­ample of this pat­tern. With this usage, the SOAP mes­sage body (an XML doc­u­ment) con­tains the return value from the method that was in­voked. This ex­ample from the SOAP spe­cific­a­tion re­turns the answer from in­vok­ing the Get­LastTrade­Price method.

                                                                                                                          
                                                                                                                          <SOAP-ENV:信封
                                                                                                                            xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
                                                                                                                            SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap
                                                                                                                          图形/ccc.gif/编码/“/>
                                                                                                                             <SOAP-ENV:正文>
                                                                                                                                 <m:GetLastTradePriceResponse xmlns:m="Some-URI">
                                                                                                                                     <价格>34.5</价格>
                                                                                                                                 </m:获取最后交易价格响应>
                                                                                                                             </SOAP-ENV:正文>
                                                                                                                          </SOAP-ENV:信封>
                                                                                                                          
                                                                                                                          
                                                                                                                          <SOAP-ENV:En­vel­ope
                                                                                                                            xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/en­vel­ope/"
                                                                                                                            SOAP-ENV:en­cod­ing­Style="http://schemas.xmlsoap.org/soap
                                                                                                                          /en­cod­ing/"/>
                                                                                                                             <SOAP-ENV:Body>
                                                                                                                                 <m:Get­LastTrade­PriceResponse xmlns:m="Some-URI">
                                                                                                                                     <Price>34.5</Price>
                                                                                                                                 </m:Get­LastTrade­PriceResponse>
                                                                                                                             </SOAP-ENV:Body>
                                                                                                                          </SOAP-ENV:En­vel­ope>
                                                                                                                          



                                                                                                                            活动讯息

                                                                                                                            Event Message

                                                                                                                            图形/eventmessage_icon.gif

                                                                                                                            一些应用程序希望使用事件通知来协调它们的操作,并希望使用消息传递来传达这些事件。

                                                                                                                            Sev­eral ap­plic­a­tions would like to use event no­ti­fic­a­tion to co­ordin­ate their ac­tions and would like to use Mes­saging to com­mu­nic­ate those events.

                                                                                                                            如何使用消息传递将事件从一个应用程序传输到另一个应用程序?

                                                                                                                            How can mes­saging be used to trans­mit events from one ap­plic­a­tion to an­other?



                                                                                                                            有时,一个对象中发生了另一个对象需要了解的事件。典型的例子是模型-视图-控制器架构[ POSA ],其中模型更改其状态并且必须通知其视图以便它们可以重新绘制自己。这种更改通知在分布式系统中也很有用。例如,在 B2B 系统中,一个企业可能需要通知其他企业价格变化或全新产品目录。

                                                                                                                            Some­times, an event occurs in one object that an­other object needs to know about. The clas­sic ex­ample is a Model-View-Con­trol­ler ar­chi­tec­ture [POSA], where the model changes its state and must notify its views so that they can redraw them­selves. Such change no­ti­fic­a­tion can also be useful in dis­trib­uted sys­tems. For ex­ample, in a B2B system, one busi­ness may need to notify others of price changes or a whole new product cata­log.

                                                                                                                            进程可以使用远程过程调用来通知其他应用程序更改事件,但这要求接收者立即接受事件,即使它现在不需要事件。RPC 还要求通告进程知道每个侦听器进程并在每个侦听器上调用 RPC。

                                                                                                                            A pro­cess can use Remote Pro­ced­ure In­voc­a­tion to notify other ap­plic­a­tions of change events, but that re­quires that the re­ceiver accept the event im­me­di­ately, even if it doesn't want events right now. RPC also re­quires that the an­noun­cing pro­cess know every listener pro­cess and invoke an RPC on each listener.

                                                                                                                            观察者模式[ GoF]描述了如何设计一个宣布事件的主题和消费事件的观察者。主体通过调用观察者的Update()方法向观察者通知事件。Update()可以作为 RPC 实现,但它具有 RPC 的所有缺点。

                                                                                                                            The Ob­server pat­tern [GoF] de­scribes how to design a sub­ject that an­nounces events and ob­serv­ers that con­sume events. A sub­ject no­ti­fies an ob­server of an event by call­ing the ob­server's Update() method. Update() can be im­ple­men­ted as an RPC, but it would have all of RPC's short­com­ings.

                                                                                                                            最好以Message 的形式异步发送事件通知。这样,主体可以在准备好时发送通知,并且每个观察者都可以在准备好时接收通知。

                                                                                                                            It would be better to send the event no­ti­fic­a­tion asyn­chron­ously, as a Mes­sage. This way, the sub­ject can send the no­ti­fic­a­tion when it's ready, and each ob­server can re­ceive the no­ti­fic­a­tion if and when it's ready.

                                                                                                                            使用事件消息在应用程序之间提供可靠的异步事件通知。

                                                                                                                            Use an Event Mes­sage for re­li­able, asyn­chron­ous event no­ti­fic­a­tion between ap­plic­a­tions.

                                                                                                                            图形/05inf03.gif



                                                                                                                            当主题有一个事件要宣布时,它会创建一个事件对象,将其包装在消息中,并将其作为事件消息发送到通道上。 观察者接收事件消息,获取事件并处理它。消息传递不会更改正在宣布的事件,而只是确保通知到达观察者。

                                                                                                                            When a sub­ject has an event to an­nounce, it cre­ates an event object, wraps it in a mes­sage, and sends it on a chan­nel as an Event Mes­sage. The ob­server re­ceives the Event Mes­sage, gets the event, and pro­cesses it. Mes­saging does not change the event that's being an­nounced, but just makes sure that the no­ti­fic­a­tion gets to the ob­server.

                                                                                                                            事件消息可以是消息传递系统中的任何类型的消息。在 Java 中,事件可以是对象或数据,例如 XML 文档。因此,它可以通过 JMS 作为ObjectMessage 、 TextMessage等进行传输。在 .NET 中,事件消息是存储有事件的消息。

                                                                                                                            An Event Mes­sage can be any kind of mes­sage in the mes­saging system. In Java, an event can be an object or data, such as an XML doc­u­ment. Thus, it can be trans­mit­ted through JMS as an Ob­ject­Mes­sage, Text­Mes­sage, and so on. In .NET, an event mes­sage is a Mes­sage with the event stored in it.

                                                                                                                            事件消息文档消息之间的区别在于时间和内容的问题。事件的内容通常不太重要。许多事件甚至有一个空的消息正文;它们的出现就足以让观察者做出反应。活动的时机非常重要;一旦发生变化,主体应该立即发出一个事件,而观察者应该在事件仍然相关时快速处理它。保证交付通常对事件没有太大帮助,因为事件频繁且需要快速交付。消息过期对于确保快速处理事件或根本不处理事件非常有帮助。

                                                                                                                            The dif­fer­ence between an Event Mes­sage and a Doc­u­ment Mes­sage is a matter of timing and con­tent. An event's con­tents are typ­ic­ally less im­port­ant. Many events even have an empty mes­sage body; their mere oc­cur­rence tells the ob­server to react. An event's timing is very im­port­ant; the sub­ject should issue an event as soon as a change occurs, and the ob­server should pro­cess it quickly while it's still rel­ev­ant. Guar­an­teed De­liv­ery is usu­ally not very help­ful with events, be­cause they're fre­quent and need to be de­livered quickly. Mes­sage Ex­pir­a­tion can be very help­ful to make sure that an event is pro­cessed quickly or not at all.

                                                                                                                            例如,必须通知其他企业价格或产品变化的 B2B 系统可以使用事件消息、文档消息或两者的组合。如果一条消息表明计算机磁盘驱动器的价格已发生变化,则这是一个事件。如果该消息提供有关磁盘驱动器的信息(包括其新价格),则该消息是作为事件发送的文档。宣布新目录及其 URL 的另一条消息是一个事件,而实际包含新目录的类似消息是一个包含文档的事件。

                                                                                                                            The B2B system that must notify other busi­nesses of price or product changes, for ex­ample, could use Event Mes­sages, Doc­u­ment Mes­sages, or a com­bin­a­tion of the two. If a mes­sage says that the price for com­puter disk drives has changed, that's an event. If the mes­sage provides in­form­a­tion about the disk drive, in­clud­ing its new price, that's a doc­u­ment being sent as an event. An­other mes­sage that an­nounces the new cata­log and its URL is an event, whereas a sim­ilar mes­sage that ac­tu­ally con­tains the new cata­log is an event that con­tains a doc­u­ment.

                                                                                                                            哪个更好?观察者模式将其描述为推模型和拉模型之间的权衡。推送模型发送有关更改的信息作为更新的一部分,而拉取模型发送最少的信息,需要更多信息的观察者通过向主题发送GetState()来请求它。这两个模型与这样的消息传递相关。

                                                                                                                            Which is better? The Ob­server pat­tern de­scribes this as a trade-off between a push model and a pull model. The push model sends in­form­a­tion about the change as part of the update, whereas the pull model sends min­imal in­form­a­tion, and ob­serv­ers that want more in­form­a­tion re­quest it by send­ing Get­State() to the sub­ject. The two models relate to mes­saging like this.

                                                                                                                            推送模型 消息是组合的文档/事件消息;消息的传递宣告状态已经发生并且消息的内容是新的状态。如果所有观察者都想要这些细节,这会更有效,但否则可能会是两全其美的情况:频繁发送的大消息常常被许多观察者忽略。

                                                                                                                            拉模型 有三个消息:

                                                                                                                            1. 更新是通知事件观察者的事件消息。

                                                                                                                            2. 状态请求是感兴趣的观察者用来向主题请求详细信息的命令消息。

                                                                                                                            3. 状态回复是主体用来向观察者发送详细信息的文档消息。

                                                                                                                            Push model The mes­sage is a com­bined doc­u­ment/event mes­sage; the mes­sage's de­liv­ery an­nounces that the state has oc­curred and the mes­sage's con­tents are the new state. This is more ef­fi­cient if all ob­serv­ers want these de­tails, but oth­er­wise it can be the worst of both worlds: a large mes­sage that is sent fre­quently and often ig­nored by many ob­serv­ers.

                                                                                                                            Pull model There are three mes­sages:

                                                                                                                            1. Update is an Event Mes­sage that no­ti­fies the ob­server of the event.

                                                                                                                            2. State Re­quest is a Com­mand Mes­sage an in­ter­ested ob­server uses to re­quest de­tails from the sub­ject.

                                                                                                                            3. State Reply is a Doc­u­ment Mes­sage the sub­ject uses to send the de­tails to the ob­server.

                                                                                                                            拉模型的优点是更新消息很小,只有感兴趣的观察者请求详细信息,并且每个感兴趣的观察者都可以请求它特别感兴趣的详细信息。缺点是需要额外的通道数量以及由此产生的流量不止一条消息。

                                                                                                                            The ad­vant­ages of the pull model are that the update mes­sages are small, only in­ter­ested ob­serv­ers re­quest de­tails, and po­ten­tially each in­ter­ested ob­server can re­quest the de­tails it spe­cific­ally is in­ter­ested in. The dis­ad­vant­age is the ad­di­tional number of chan­nels needed and the res­ult­ing traffic caused by more than one mes­sage.

                                                                                                                            有关如何使用消息传递实现Observer的更多详细信息,请参阅第 6 章“插曲:简单消息传递”中的“JMS 发布/订阅示例”部分。

                                                                                                                            For more de­tails on how to im­ple­ment Ob­server using mes­saging, see the sec­tion "JMS Pub­lish/Sub­scribe Ex­ample" in Chapter 6, "In­ter­lude: Simple Mes­saging."

                                                                                                                            通常没有理由将事件消息限制为通过点对点通道发送给单个接收者;该消息通常通过发布-订阅通道广播,以便所有感兴趣的进程都收到通知。虽然文档消息需要被消耗以便文档不丢失,但事件消息的接收者通常会在太忙而无法处理消息时忽略这些消息,因此订阅者通常可能是非持久的(不是持久订阅者) 事件消息是使用消息传递实现观察者

                                                                                                                            There is usu­ally no reason to limit an event mes­sage to a single re­ceiver via a Point-to-Point Chan­nel; the mes­sage is usu­ally broad­cast via a Pub­lish-Sub­scribe Chan­nel so that all in­ter­ested pro­cesses re­ceive no­ti­fic­a­tion. Whereas a Doc­u­ment Mes­sage needs to be con­sumed so that the doc­u­ment is not lost, a re­ceiver of Event Mes­sages can often ignore the mes­sages when it's too busy to pro­cess them, so the sub­scribers can often be non­dur­able (not Dur­able Sub­scribers). Event Mes­sage is a key part of im­ple­ment­ing the Ob­server pat­tern using mes­saging.

                                                                                                                              请求-回复

                                                                                                                              Request-Reply

                                                                                                                              图形/requestreply_icon.gif

                                                                                                                              当两个应用程序通过消息传递进行通信时,该通信是单向的。应用程序可能需要双向对话。

                                                                                                                              When two ap­plic­a­tions com­mu­nic­ate via Mes­saging, the com­mu­nic­a­tion is one-way. The ap­plic­a­tions may want a two-way con­ver­sa­tion.

                                                                                                                              当应用程序发送消息时,如何从接收者那里得到响应?

                                                                                                                              When an ap­plic­a­tion sends a mes­sage, how can it get a re­sponse from the re­ceiver?



                                                                                                                              消息传递提供消息在消息通道上沿一个传播它们从发送者传输到接收者。这种异步传输使传送更加可靠,并将发送者与接收者解耦。

                                                                                                                              Mes­saging provides one-way com­mu­nic­a­tion between ap­plic­a­tions. Mes­sages travel on a Mes­sage Chan­nel in one dir­ec­tion; they travel from the sender to the re­ceiver. This asyn­chron­ous trans­mis­sion makes the de­liv­ery more re­li­able and de­couples the sender from the re­ceiver.

                                                                                                                              问题在于组件之间的通信通常需要是双向的。当程序调用函数时,它会收到一个返回值。当它执行查询时,它会接收查询结果。当一个组件通知另一个组件发生更改时,它可能希望收到确认。消息传递如何能够是双向的?

                                                                                                                              The prob­lem is that com­mu­nic­a­tion between com­pon­ents often needs to be two-way. When a pro­gram calls a func­tion, it re­ceives a return value. When it ex­ecutes a query, it re­ceives query res­ults. When one com­pon­ent no­ti­fies an­other of a change, it may want to re­ceive an ac­know­ledg­ment. How can mes­saging be two-way?

                                                                                                                              也许发送者和接收者可以同时共享消息。然后,每个应用程序都可以将信息添加到消息中以供其他应用程序使用。但这不是消息传递的工作原理。消息是先发送后接收的,因此发送者和接收者不能同时访问该消息。

                                                                                                                              Per­haps a sender and re­ceiver could share a mes­sage sim­ul­tan­eously. Then, each ap­plic­a­tion could add in­form­a­tion to the mes­sage for the other to con­sume. But that is not how mes­saging works. A mes­sage is first sent and then re­ceived, so the sender and re­ceiver cannot both access the mes­sage at the same time.

                                                                                                                              也许发件人可以保留对该消息的引用。然后,一旦接收者将其响应放入消息中,发送者就可以拉回消息。这可能适用于夹在晾衣绳上的笔记,但这不是消息通道的工作方式。通道在一个方向上传输消息。所需要的是双向通道上的双向消息。

                                                                                                                              Per­haps the sender could keep a ref­er­ence to the mes­sage. Then, once the re­ceiver placed its re­sponse into the mes­sage, the sender could pull the mes­sage back. This may work for notes clipped to a clothesline, but it is not how a Mes­sage Chan­nel works. A chan­nel trans­mits mes­sages in one dir­ec­tion. What is needed is a two-way mes­sage on a two-way chan­nel.

                                                                                                                              发送一对请求-答复消息,每个消息都在其自己的通道上。

                                                                                                                              Send a pair of Re­quest-Reply mes­sages, each on its own chan­nel.

                                                                                                                              图形/05inf04.gif



                                                                                                                              请求-回复有两个参与者:

                                                                                                                              Re­quest-Reply has two par­ti­cipants:

                                                                                                                              1. 请求者发送请求消息并等待回复消息。

                                                                                                                              2. Re­questor sends a re­quest mes­sage and waits for a reply mes­sage.

                                                                                                                              3. 回复者接收请求消息并用回复消息进行响应。

                                                                                                                              4. Replier re­ceives the re­quest mes­sage and re­sponds with a reply mes­sage.

                                                                                                                              请求通道可以是点对点通道或发布订阅通道。区别在于请求是否应该广播给所有感兴趣的各方,或者应该仅由单个消费者处理。另一方面,回复通道几乎总是点对点的,因为广播回复通常没有意义——它们应该只返回给请求者。

                                                                                                                              The re­quest chan­nel can be a Point-to-Point Chan­nel or a Pub­lish-Sub­scribe Chan­nel. The dif­fer­ence is whether the re­quest should be broad­cas­ted to all in­ter­ested parties or should be pro­cessed by only a single con­sumer. The reply chan­nel, on the other hand, is almost always point-to-point, be­cause it usu­ally makes no sense to broad­cast repli­esthey should be re­turned only to the re­questor.

                                                                                                                              当调用者执行远程过程调用时,调用者的线程在等待响应时必须阻塞。通过Request-Reply,请求者有两种接收回复的方法。

                                                                                                                              When a caller per­forms a Remote Pro­ced­ure In­voc­a­tion, the caller's thread must block while it waits for the re­sponse. With Re­quest-Reply, the re­questor has two ap­proaches for re­ceiv­ing the reply.

                                                                                                                              1. 同步块 调用者中的单个线程发送请求消息,阻塞(作为轮询消费者)等待回复消息,然后处理回复。这实现起来很简单,但如果请求者崩溃,则很难重新建立被阻塞的线程。等待响应的请求线程意味着只有一个未完成的请求,或者该请求的回复通道对于该线程是私有的。

                                                                                                                              2. Syn­chron­ous Block A single thread in the caller sends the re­quest mes­sage, blocks (as a Polling Con­sumer) to wait for the reply mes­sage, and then pro­cesses the reply. This is simple to im­ple­ment, but if the re­questor crashes, it will have dif­fi­culty rees­tab­lish­ing the blocked thread. The re­quest thread await­ing the re­sponse im­plies that there is only one out­stand­ing re­quest or that the reply chan­nel for this re­quest is private for this thread.

                                                                                                                              3. 异步回调 调用者中的 一个线程发送请求消息并为回复设置回调。一个单独的线程侦听回复消息。当回复消息到达时,回复线程调用适当的回调,该回调重新建立调用者的上下文并处理回复。这种方法允许多个未完成的请求共享单个回复通道和单个回复线程来处理多个请求线程的回复。如果请求者崩溃,只需重新启动回复线程即可恢复。然而,一个额外的复杂性是回调机制必须重新建立调用者的上下文。

                                                                                                                              4. Asyn­chron­ous Call­back One thread in the caller sends the re­quest mes­sage and sets up a call­back for the reply. A sep­ar­ate thread listens for reply mes­sages. When a reply mes­sage ar­rives, the reply thread in­vokes the ap­pro­pri­ate call­back, which rees­tab­lishes the caller's con­text and pro­cesses the reply. This ap­proach en­ables mul­tiple out­stand­ing re­quests to share a single reply chan­nel and a single reply thread to pro­cess replies for mul­tiple re­quest threads. If the re­questor crashes, it can re­cover by simply re­start­ing the reply thread. An added com­plex­ity, how­ever, is the call­back mech­an­ism that must rees­tab­lish the caller's con­text.

                                                                                                                              两个应用程序相互发送请求和回复并不是很有帮助。有趣的是这两条消息代表什么。

                                                                                                                              Two ap­plic­a­tions send­ing re­quests and replies to each other are not very help­ful. What is in­ter­est­ing is what the two mes­sages rep­res­ent.

                                                                                                                              1. 消息传递 RPC这是使用消息传递 实现远程过程调用的方法。该请求是一条命令消息,描述了应答者应调用的功能。回复是包含函数的返回值或异常的文档消息。

                                                                                                                              2. Mes­saging RPC This is how to im­ple­ment Remote Pro­ced­ure In­voc­a­tion using mes­saging. The re­quest is a Com­mand Mes­sage that de­scribes the func­tion the replier should invoke. The reply is a Doc­u­ment Mes­sage that con­tains the func­tion's return value or ex­cep­tion.

                                                                                                                              3. 消息传递查询 这是使用消息传递执行远程查询的方法。请求是包含查询的命令消息,回复是查询的结果,可能是消息序列

                                                                                                                              4. Mes­saging Query This is how to per­form a remote query using mes­saging. The re­quest is a Com­mand Mes­sage con­tain­ing the query, and the reply is the res­ults of the query, per­haps a Mes­sage Se­quence.

                                                                                                                              5. 通知/确认 这 提供了使用消息传递的事件通知和确认。请求是提供通知的事件消息,回复是确认通知的文档消息。该确认本身可能是另一项请求,即寻求有关事件的详细信息的请求。

                                                                                                                              6. Notify/Ac­know­ledge This provides for event no­ti­fic­a­tion with ac­know­ledg­ment, using mes­saging. The re­quest is an Event Mes­sage that provides no­ti­fic­a­tion, and the reply is a Doc­u­ment Mes­sage ac­know­ledging the no­ti­fic­a­tion. The ac­know­ledg­ment may itself be an­other re­quest, one seek­ing de­tails about the event.

                                                                                                                              该请求就像一个方法调用。因此,答复是三种可能性之一:

                                                                                                                              The re­quest is like a method call. As such, the reply is one of three pos­sib­il­it­ies:

                                                                                                                              1. Void 只是通知调用者该方法已完成,以便调用者可以继续。

                                                                                                                              2. Void Simply no­ti­fies the caller that the method has fin­ished so that the caller can pro­ceed.

                                                                                                                              3. 结果值 作为方法的返回值的单个对象。

                                                                                                                              4. Result value A single object that is the method's return value.

                                                                                                                              5. Exception 单个异常对象,指示该方法在成功完成之前中止,并指示原因。

                                                                                                                              6. Ex­cep­tion A single ex­cep­tion object in­dic­at­ing that the method abor­ted before com­plet­ing suc­cess­fully, and in­dic­at­ing why.

                                                                                                                              请求应包含返回地址,以告诉回复者将回复发送到何处。回复应包含一个相关标识符,指定此回复针对哪个请求。

                                                                                                                              The re­quest should con­tain a Return Ad­dress to tell the replier where to send the reply. The reply should con­tain a Cor­rel­a­tion Iden­ti­fier that spe­cifies which re­quest this reply is for.

                                                                                                                              示例: SOAP 1.1 消息

                                                                                                                              Ex­ample: SOAP 1.1 Mes­sages

                                                                                                                              SOAP 消息以请求-答复对的形式出现。SOAP 请求消息指示发送者想要在接收者上调用的服务,而 SOAP 响应消息则包含服务调用的结果。响应消息包含结果值或错误(相当于异常的 SOAP [ SOAP 1.1 ])。

                                                                                                                              SOAP mes­sages come in Re­quest-Reply pairs. A SOAP re­quest mes­sage in­dic­ates a ser­vice the sender wants to invoke on the re­ceiver, whereas a SOAP re­sponse mes­sage con­tains the result of the ser­vice in­voc­a­tion. The re­sponse mes­sage con­tains either a result value or a faultthe SOAP equi­val­ent of an ex­cep­tion [SOAP 1.1].



                                                                                                                              示例: SOAP 1.2 响应消息交换模式

                                                                                                                              Ex­ample: SOAP 1.2 Re­sponse Mes­sage Ex­change Pat­tern

                                                                                                                              SOAP 1.1 松散地描述了响应消息,而 SOAP 1.2 引入了显式请求-响应消息交换模式[ SOAP 1.2 第 2 部分 ]。该模式描述了对 SOAP 请求的单独的、可能异步的响应。

                                                                                                                              Whereas SOAP 1.1 has loosely de­scribed re­sponse mes­sages, SOAP 1.2 in­tro­duces an ex­pli­cit Re­quest-Re­sponse Mes­sage Ex­change pat­tern [SOAP 1.2 Part 2]. This pat­tern de­scribes a sep­ar­ate, po­ten­tially asyn­chron­ous re­sponse to a SOAP re­quest.



                                                                                                                              示例: JMS 请求者对象

                                                                                                                              Ex­ample: JMS Re­questor Ob­jects

                                                                                                                              JMS 包含一些可用于实现Request-Reply 的功能。

                                                                                                                              JMS in­cludes a couple of fea­tures that can be used to im­ple­ment Re­quest-Reply.

                                                                                                                              TemporaryQueue可以通过编程方式创建的队列,并且其持续时间与用于创建它的连接一样长。只有由同一连接创建的MessageConsumers才能从队列中读取数据,因此它对于连接来说实际上是私有的 [JMS 1.1]

                                                                                                                              A Tem­por­aryQueue is a Queue that can be cre­ated pro­gram­mat­ic­ally and that lasts only as long as the Con­nec­tion used to create it. Only Mes­sage­Con­sumers cre­ated by the same con­nec­tion can read from the queue, so it is ef­fect­ively private to the con­nec­tion [JMS 1.1].

                                                                                                                              MessageProducers如何知道这个新创建的私有队列?请求者创建一个临时队列并在请求消息的reply-to属性中指定它(请参阅返回地址)。行为良好的回复者会将回复发送回指定的队列,如果它不是请求消息的属性,回复者甚至不会知道该队列。这是请求者可以使用的一种简单方法,以确保始终得到回复。

                                                                                                                              How do Mes­sage­Pro­du­cers know about this newly cre­ated, private queue? A re­questor cre­ates a tem­por­ary queue and spe­cifies it in the reply-to prop­erty of a re­quest mes­sage (see Return Ad­dress). A well-be­haved replier will send the reply back on the spe­cified queue, one that the replier wouldn't even know about if it weren't a prop­erty of the re­quest mes­sage. This is a simple ap­proach the re­questor can use to make sure that the replies always come back to it.

                                                                                                                              临时队列的缺点是,当连接关闭时,队列及其中的所有消息都会被删除。同样,临时队列无法提供保证交付;如果消息系统崩溃,那么连接就会丢失,因此队列及其消息也会丢失。

                                                                                                                              The down­side with tem­por­ary queues is that when their Con­nec­tion closes, the queue and any mes­sages in it are de­leted. Like­wise, tem­por­ary queues cannot provide Guar­an­teed De­liv­ery; if the mes­saging system crashes, then the con­nec­tion is lost, so the queue and its mes­sages are lost.

                                                                                                                              JMS 还提供了QueueRequestor ,一个用于发送请求和接收回复的简单类。请求者包含用于发送请求的QueueSender和用于接收回复的 QueueReceiver 。每个请求者创建自己的临时队列来接收回复,并在请求的回复属性中指定该队列[ JMS 1.1 ]。请求者使得发送请求和接收回复变得非常简单:

                                                                                                                              JMS also provides QueueRe­questor, a simple class for send­ing re­quests and re­ceiv­ing replies. A re­questor con­tains a QueueSender for send­ing re­quests and a QueueRe­ceiver for re­ceiv­ing replies. Each re­questor cre­ates its own tem­por­ary queue for re­ceiv­ing replies and spe­cifies that in the re­quest's reply-to prop­erty [JMS 1.1]. A re­questor makes send­ing a re­quest and re­ceiv­ing a reply very simple:

                                                                                                                              
                                                                                                                              QueueConnection connection = // 获取连接
                                                                                                                              Queue requestQueue = // 获取队列
                                                                                                                              Message request = // 创建请求消息
                                                                                                                              QueueSession 会话 = connection.createQueueSession(false,
                                                                                                                              图形/ccc.gif会话.AUTO_ACKNOWLEDGE);
                                                                                                                              QueueRequestor 请求者 = new QueueRequestor(会话,
                                                                                                                              图形/ccc.gif请求队列);
                                                                                                                              消息回复 = requestor.request(request);
                                                                                                                              
                                                                                                                              
                                                                                                                              QueueCon­nec­tion con­nec­tion = // obtain the con­nec­tion
                                                                                                                              Queue re­questQueue = // obtain the queue
                                                                                                                              Mes­sage re­quest = // create the re­quest mes­sage
                                                                                                                              QueueSes­sion ses­sion = con­nec­tion.cre­ateQueueSes­sion(false,
                                                                                                                               Ses­sion.AUTO_AC­KNOW­LEDGE);
                                                                                                                              QueueRe­questor re­questor = new QueueRe­questor(ses­sion,
                                                                                                                               re­questQueue );
                                                                                                                              Mes­sage reply = re­questor.re­quest(re­quest);
                                                                                                                              

                                                                                                                              一种方法请求发送请求消息并阻塞,直到收到回复消息。

                                                                                                                              One methodre­questsends the re­quest mes­sage and blocks until it re­ceives the reply mes­sage.

                                                                                                                              QueueRequestor使用的 TemporaryQueue是一个点对点Channel 。它的发布-订阅通道等效项是TemporaryTopic 和TopicRequestor

                                                                                                                              Tem­por­aryQueue, used by QueueRe­questor, is a Point-to-Point Chan­nel. Its Pub­lish-Sub­scribe Chan­nel equi­val­ents are Tem­por­a­ryTopic and TopicRe­questor.



                                                                                                                                退货地址

                                                                                                                                Return Address

                                                                                                                                图形/returnaddress_icon.gif

                                                                                                                                我的应用程序正在使用消息传递来执行Request -Reply 。

                                                                                                                                My ap­plic­a­tion is using Mes­saging to per­form a Re­quest-Reply.

                                                                                                                                回复者如何知道将回复发送到哪里?

                                                                                                                                How does a replier know where to send the reply?



                                                                                                                                消息通常被认为是完全独立的,因此任何发送者都可以随时在任何通道上发送消息。然而,消息通常是相关联的。对于请求-应答对,两条消息看起来是独立的,但是应答消息与引起它的请求消息具有一一对应的关系。因此,处理请求消息的应答者不能简单地在它想要的任何通道上发送应答消息;它必须将其发送到请求者期望回复的通道上。

                                                                                                                                Mes­sages are often thought of as com­pletely in­de­pend­ent, such that any sender can send a mes­sage on any chan­nel whenever it likes. How­ever, mes­sages are often as­so­ci­ated. With Re­quest-Reply pairs, two mes­sages appear in­de­pend­ent, but the reply mes­sage has a one-to-one cor­res­pond­ence with the re­quest mes­sage that caused it. Thus, the replier that pro­cesses the re­quest mes­sage cannot simply send the reply mes­sage on any chan­nel it wants; it must send it on the chan­nel on which the re­questor ex­pects the reply.

                                                                                                                                每个接收者都可以自动知道在哪个通道上发送回复,但是硬编码这样的假设会使软件不太灵活并且更难以维护。此外,单个回复者可以处理来自多个不同请求者的呼叫,因此每条消息的回复通道都不相同;这取决于哪个请求者发送了请求消息。

                                                                                                                                Each re­ceiver could auto­mat­ic­ally know which chan­nel to send replies on, but hard-coding such as­sump­tions makes the soft­ware less flex­ible and more dif­fi­cult to main­tain. Fur­ther­more, a single replier could be pro­cess­ing calls from sev­eral dif­fer­ent re­questors, so the reply chan­nel is not the same for every mes­sage; it de­pends on which re­questor sent the re­quest mes­sage.

                                                                                                                                不确定将回复发送到哪里

                                                                                                                                Un­cer­tain Where to Send Replies

                                                                                                                                图形/05inf05.gif

                                                                                                                                请求者可能不希望将回复发送回自己。相反,它可以具有关联的回调处理器来处理回复,并且回调处理器可以监视与请求者不同的通道(或者请求者可以根本不监视任何通道)。请求者可以有多个回调处理器,要求将来自同一请求者的不同请求的回复发送到不同的处理器。

                                                                                                                                A re­questor po­ten­tially may not want a reply sent back to itself. Rather, it may have an as­so­ci­ated call­back pro­cessor to pro­cess replies, and the call­back pro­cessor may mon­itor a dif­fer­ent chan­nel than the re­questor does (or the re­questor may not mon­itor any chan­nels at all). The re­questor could have mul­tiple call­back pro­cessors, re­quir­ing replies for dif­fer­ent re­quests from the same re­questor to be sent to dif­fer­ent pro­cessors.

                                                                                                                                回复通道不一定会将回复传输回请求者;它将把它们传输给请求者想要处理回复的任何人,因为它正在侦听请求者指定的通道。因此,知道哪个请求者发送了请求或者请求在哪个通道上发送并不一定告诉回复者在哪个通道上发送回复。即使这样做,回复者仍然必须推断对特定请求者或请求通道使用哪个回复通道。请求更容易明确指定要使用的回复通道。请求者需要一种方法来告诉回复者在哪里以及如何发回回复。

                                                                                                                                The reply chan­nel will not ne­ces­sar­ily trans­mit replies back to the re­questor; it will trans­mit them to whomever the re­questor wants to pro­cess the replies, be­cause it's listen­ing to the chan­nel the re­questor spe­cified. So, know­ing what re­questor sent a re­quest or what chan­nel it was sent on does not ne­ces­sar­ily tell the replier what chan­nel to send the reply on. Even if it did, the replier would still have to infer which reply chan­nel to use for a par­tic­u­lar re­questor or re­quest chan­nel. It's easier for the re­quest to ex­pli­citly spe­cify which reply chan­nel to use. What is needed is a way for the re­questor to tell the replier where and how to send back a reply.

                                                                                                                                请求消息应包含返回地址,指示将回复消息发送到何处。

                                                                                                                                The re­quest mes­sage should con­tain a Return Ad­dress that in­dic­ates where to send the reply mes­sage.

                                                                                                                                图形/05inf06.gif



                                                                                                                                这样,回复者不需要知道将回复发送到哪里;它只需从请求消息中获取回复通道地址即可。如果发送给同一回复者的不同消息需要回复到不同的位置,则回复者可以根据每个请求消息确定将该请求的回复发送到哪里。这封装了请求者内用于请求和回复的通道的知识,因此这些决策不必在回复者内进行硬编码。返回地址被放置在消息的标头中,因为它不是正在传输的应用程序数据的一部分。

                                                                                                                                This way, the replier does not need to know where to send the reply; it can just obtain the reply chan­nel ad­dress from the re­quest mes­sage. If dif­fer­ent mes­sages to the same replier re­quire replies to dif­fer­ent places, the replier can de­term­ine from each re­quest mes­sage where to send the reply for that re­quest. This en­cap­su­lates the know­ledge of what chan­nels to use for re­quests and replies within the re­questor so those de­cisions do not have to be hard-coded within the replier. A Return Ad­dress is put in the header of a mes­sage be­cause it's not part of the ap­plic­a­tion data being trans­mit­ted.

                                                                                                                                消息的返回地址类似于电子邮件中的回复字段。回复电子邮件地址通常与发件人地址相同,但发件人可以将其设置为不同的地址以便在用于发送原始邮件的帐户以外的帐户中接收回复。

                                                                                                                                A mes­sage's Return Ad­dress is ana­log­ous to the reply-to field in an e-mail mes­sage. The reply-to e-mail ad­dress is usu­ally the same as the from ad­dress, but the sender can set it to a dif­fer­ent ad­dress to re­ceive replies in an ac­count other than the one used to send the ori­ginal mes­sage.

                                                                                                                                当回复通过返回地址指示的通道发回时可能还需要一个相关标识符。返回地址告诉接收者将回复消息放在哪个通道上;相关标识符告诉发送者回复是针对哪个请求的。

                                                                                                                                When the reply is sent back over the chan­nel in­dic­ated by the Return Ad­dress, it may also need a Cor­rel­a­tion Iden­ti­fier. The Return Ad­dress tells the re­ceiver what chan­nel to put the reply mes­sage on; the Cor­rel­a­tion Iden­ti­fier tells the sender which re­quest a reply is for.

                                                                                                                                示例: JMS 回复属性

                                                                                                                                Ex­ample: JMS Reply-To Prop­erty

                                                                                                                                JMS 消息具有返回地址的预定义属性JMSReplyTo 。它的类型是目的地(主题队列),而不仅仅是目的地名称的字符串,这确保了目的地(例如,消息通道)确实存在,至少在发送请求时[JMS 1.1 ],[ Monson-哈菲尔]。

                                                                                                                                JMS mes­sages have a pre­defined prop­erty for Return Ad­dresses, JM­S­ReplyTo. Its type is a Des­tin­a­tion (a Topic or Queue) rather than just a string for the des­tin­a­tion name, which en­sures that the des­tin­a­tion (e.g., Mes­sage Chan­nel) really exists, at least when the re­quest is sent [JMS 1.1], [Monson-Haefel].

                                                                                                                                希望指定作为队列的回复通道的发送者可以这样做:

                                                                                                                                A sender that wishes to spe­cify a reply chan­nel that is a Queue would do so like this:

                                                                                                                                Queue requestQueue = // 指定请求目的地
                                                                                                                                QueuereplyQueue = //指定回复目的地
                                                                                                                                Message requestMessage = // 创建请求消息
                                                                                                                                requestMessage.setJMSReplyTo(replyQueue);
                                                                                                                                MessageProducer requestSender = session.createProducer(requestQueue);
                                                                                                                                requestSender.send(requestMessage);
                                                                                                                                
                                                                                                                                Queue re­questQueue = // Spe­cify the re­quest des­tin­a­tion
                                                                                                                                Queue replyQueue = // Spe­cify the reply des­tin­a­tion
                                                                                                                                Mes­sage re­quest­Mes­sage = // Create the re­quest mes­sage
                                                                                                                                re­quest­Mes­sage.setJM­S­ReplyTo(replyQueue);
                                                                                                                                Mes­sage­Pro­du­cer re­quest­Sender = ses­sion.cre­ate­Pro­du­cer(re­questQueue);
                                                                                                                                re­quest­Sender.send(re­quest­Mes­sage);
                                                                                                                                

                                                                                                                                然后,接收方将发送如下回复消息:

                                                                                                                                Then, the re­ceiver would send the reply mes­sage like this:

                                                                                                                                
                                                                                                                                Queue requestQueue = // 指定请求目的地
                                                                                                                                MessageConsumer requestReceiver = session.createConsumer
                                                                                                                                图形/ccc.gif(请求队列);
                                                                                                                                消息 requestMessage = requestReceiver.receive();
                                                                                                                                MessagereplyMessage = // 创建回复消息
                                                                                                                                目标回复队列 = requestMessage.getJMSReplyTo();
                                                                                                                                MessageProducer 回复Sender = session.createProducer(replyQueue);
                                                                                                                                回复Sender.send(replyMessage);
                                                                                                                                
                                                                                                                                
                                                                                                                                Queue re­questQueue = // Spe­cify the re­quest des­tin­a­tion
                                                                                                                                Mes­sage­Con­sumer re­questReceiver = ses­sion.cre­ate­Con­sumer
                                                                                                                                (re­questQueue);
                                                                                                                                Mes­sage re­quest­Mes­sage = re­questReceiver.re­ceive();
                                                                                                                                Mes­sage replyMes­sage = // Create the reply mes­sage
                                                                                                                                Des­tin­a­tion replyQueue = re­quest­Mes­sage.getJM­S­ReplyTo();
                                                                                                                                Mes­sage­Pro­du­cer replySender = ses­sion.cre­ate­Pro­du­cer(replyQueue);
                                                                                                                                replySender.send(replyMes­sage);
                                                                                                                                



                                                                                                                                示例: .NET 响应队列属性

                                                                                                                                Ex­ample: .NET Re­sponse-Queue Prop­erty

                                                                                                                                .NET 消息还具有返回地址的预定义属性ResponseQueue 。它的类型是MessageQueue(例如, Message Channel),应用程序应将响应消息发送到[SysMsg]、[ Dickman ]队列

                                                                                                                                .NET mes­sages also have a pre­defined prop­erty for Return Ad­dresses, Re­spon­se­Queue. Its type is a Mes­sageQueue (e.g., Mes­sage Chan­nel), the queue that the ap­plic­a­tion should send a re­sponse mes­sage to [SysMsg], [Dick­man].



                                                                                                                                示例: Web 服务请求/响应

                                                                                                                                Ex­ample: Web Ser­vices Re­quest/Re­sponse

                                                                                                                                SOAP 1.2 合并了请求-响应消息交换模式[ SOAP 1.2 第 2 部分 ] ,但发送回复的地址未指定,因此是隐含的。此 SOAP 模式需要支持可选的返回地址,以真正使 SOAP 消息异步并使响应者与请求者脱钩。

                                                                                                                                SOAP 1.2 in­cor­por­ates the Re­quest-Re­sponse Mes­sage Ex­change pat­tern [SOAP 1.2 Part 2], but the ad­dress to which to send the reply is un­spe­cified and there­fore im­plied. This SOAP pat­tern will need to sup­port an op­tional Return Ad­dress to truly make SOAP mes­sages asyn­chron­ous and to delink the re­spon­der from the re­questor.

                                                                                                                                新兴的 WS-Addressing 标准通过指定如何识别 Web 服务端点以及要使用哪些 XML 元素来帮助解决这个问题。这样的地址可以在 SOAP 消息中使用来指定返回地址请参阅第 14 章“结束语”中对 WS-Addressing 的讨论。

                                                                                                                                The emer­ging WS-Ad­dress­ing stand­ard helps ad­dress this issue by spe­cify­ing how to identify a Web ser­vice en­d­point and what XML ele­ments to use. Such an ad­dress can be used in a SOAP mes­sage to spe­cify a Return Ad­dress. See the dis­cus­sion of WS-Ad­dress­ing in Chapter 14, "Con­clud­ing Re­marks."



                                                                                                                                  相关标识符

                                                                                                                                  Correlation Identifier

                                                                                                                                  图形/correlationldentifier_icon.gif

                                                                                                                                  我的应用程序正在使用消息传递来执行请求-答复并已收到答复消息。

                                                                                                                                  My ap­plic­a­tion is using Mes­saging to per­form a Re­quest-Reply and has re­ceived a reply mes­sage.

                                                                                                                                  收到回复的请求者如何知道这是针对哪个请求的回复?

                                                                                                                                  How does a re­questor that has re­ceived a reply know which re­quest this is the reply for?



                                                                                                                                  当一个进程通过远程过程调用调用另一个进程时,该调用是同步的,因此不会混淆哪个调用产生了给定结果。但消息传递是异步的,因此从调用者的角度来看,它会进行调用,然后稍后会出现结果。调用者可能甚至不记得发出过请求,或者可能发出了太多请求,以至于它不再知道这是哪一个请求的结果。现在,当调用者最终得到结果时,它可能不知道如何处理它,这违背了调用的初衷。

                                                                                                                                  When one pro­cess in­vokes an­other via Remote Pro­ced­ure In­voc­a­tion, the call is syn­chron­ous, so there is no con­fu­sion about which call pro­duced a given result. But Mes­saging is asyn­chron­ous, so from the caller's point of view, it makes the call, and then some­time later a result ap­pears. The caller may not even re­mem­ber making the re­quest, or it may have made so many re­quests that it no longer knows which one this is the result for. Now, when the caller fi­nally gets the result, it may not know what to do with it, which de­feats the pur­pose of making the call in the first place.

                                                                                                                                  无法匹配请求的回复

                                                                                                                                  Cannot Match Reply to Re­quest

                                                                                                                                  图形/05inf07.gif

                                                                                                                                  调用者可以使用多种方法来避免这种混乱。它一次只能发出一个调用,并在发送另一个请求之前等待回复,因此在任何给定时间最多有一个未完成的请求。然而,这将大大降低处理吞吐量。调用者可以假设它将按照发送请求的顺序接收回复,但消息传递并不能保证消息以什么顺序传递(请参阅重新排序器) ,并且所有请求可能不会花费相同的时间来处理,因此调用者的假设是错误的。调用者可以设计其请求,以便他们不需要回复,但这种限制将使消息传递对于许多目的来说毫无用处。

                                                                                                                                  There are a couple of ap­proaches the caller can use to avoid this con­fu­sion. It can make just one call at a time and wait for a reply before send­ing an­other re­quest, so there is at most one out­stand­ing re­quest at any given time. This, how­ever, will greatly slow pro­cess­ing through­put. The caller could assume that it will re­ceive replies in the same order it sent re­quests, but mes­saging does not guar­an­tee what order mes­sages are de­livered in (see Resequen­cer), and all re­quests may not take the same amount of time to pro­cess, so the caller's as­sump­tion would be faulty. The caller could design its re­quests so that they do not need replies, but this con­straint would make mes­saging use­less for many pur­poses.

                                                                                                                                  调用者需要的是回复消息具有指向请求消息的指针或引用,但消息并不存在于可以由变量引用的稳定内存空间中。但是,消息可能具有某种键,即唯一标识符,例如关系数据库表中行的键。这样的唯一标识符可用于从其他消息、使用该消息的客户端等中识别该消息。

                                                                                                                                  What the caller needs is for the reply mes­sage to have a pointer or ref­er­ence to the re­quest mes­sage, but mes­sages do not exist in a stable memory space where they can be ref­er­enced by vari­ables. How­ever, a mes­sage could have some sort of key, a unique iden­ti­fier like the key for a row in a re­la­tional data­base table. Such a unique iden­ti­fier could be used to identify the mes­sage from other mes­sages, cli­ents that use the mes­sage, and so on.

                                                                                                                                  每个回复消息都应包含一个Correlation Identifier ,这是一个唯一标识符,指示此回复针对哪个请求消息。

                                                                                                                                  Each reply mes­sage should con­tain a Cor­rel­a­tion Iden­ti­fier, a unique iden­ti­fier that in­dic­ates which re­quest mes­sage this reply is for.

                                                                                                                                  图形/05inf08.gif



                                                                                                                                  相关标识符有六个部分

                                                                                                                                  There are six parts to Cor­rel­a­tion Iden­ti­fier.

                                                                                                                                  1. 请求者 通过发送请求并等待回复来执行业务任务的应用程序。

                                                                                                                                  2. Re­questor An ap­plic­a­tion that per­forms a busi­ness task by send­ing a re­quest and wait­ing for a reply.

                                                                                                                                  3. 回复者 接收请求、完成请求然后发送回复的另一个应用程序。它从请求中获取请求 ID,并将其作为相关 ID 存储在回复中。

                                                                                                                                  4. Replier An­other ap­plic­a­tion that re­ceives the re­quest, ful­fills it, and then sends the reply. It gets the re­quest ID from the re­quest and stores it as the cor­rel­a­tion ID in the reply.

                                                                                                                                  5. 请求从请求者发送到回复者的 消息,包含请求 ID。

                                                                                                                                  6. Re­quest A Mes­sage sent from the re­questor to the replier, con­tain­ing a re­quest ID.

                                                                                                                                  7. 回复从回复者发送到请求者的消息 ,包含相关 ID。

                                                                                                                                  8. Reply A Mes­sage sent from the replier to the re­questor, con­tain­ing a cor­rel­a­tion ID.

                                                                                                                                  9. 请求 ID 请求中唯一标识该请求的令牌。

                                                                                                                                  10. Re­quest ID A token in the re­quest that uniquely iden­ti­fies the re­quest.

                                                                                                                                  11. 相关 ID 回复中的令牌,其值与请求中的请求 ID 相同。

                                                                                                                                  12. Cor­rel­a­tion ID A token in the reply that has the same value as the re­quest ID in the re­quest.

                                                                                                                                  这就是相关标识符的工作原理:当请求者创建请求消息时,它会为该请求分配一个请求 IDan 标识符,该标识符与所有其他当前未完成的请求(即尚未收到回复的请求)的标识符不同。当回复者处理请求时,它会保存请求 ID 并将该 ID 添加到回复中作为相关 ID。当请求者处理回复时,它使用相关ID来知道回复是针对哪个请求的。这被称为关联标识符,因为调用者使用标识符来关联(即,匹配;显示关系)每个对引起它的请求的回复。

                                                                                                                                  This is how a Cor­rel­a­tion Iden­ti­fier works: When the re­questor cre­ates a re­quest mes­sage, it as­signs the re­quest a re­quest IDan iden­ti­fier that is dif­fer­ent from those for all other cur­rently out­stand­ing re­quests, that is, re­quests that do not yet have replies. When the replier pro­cesses the re­quest, it saves the re­quest ID and adds that ID to the reply as a cor­rel­a­tion ID. When the re­questor pro­cesses the reply, it uses the cor­rel­a­tion ID to know which re­quest the reply is for. This is called a Cor­rel­a­tion Iden­ti­fier be­cause of the way the caller uses the iden­ti­fier to cor­rel­ate (i.e., match; show the re­la­tion­ship) each reply to the re­quest that caused it.

                                                                                                                                  正如消息传递的常见情况一样,请求者和回复者必须就几个细节达成一致。他们必须就请求 ID 属性的名称和类型达成一致,并且必须就相关 ID 属性的名称和类型达成一致。同样,请求和回复消息格式必须定义这些属性或允许将它们添加为自定义属性。例如,如果请求者将请求 ID 存储在名为request_id的第一级 XML 元素中,并且该值是一个整数,则回复者必须知道这一点,以便能够找到请求 ID 值并正确处理它。请求ID值和关联ID值通常是同一类型;如果不是,请求者必须知道回复者如何将请求 ID 转换为回复 ID。

                                                                                                                                  As is often the case with mes­saging, the re­questor and replier must agree on sev­eral de­tails. They must agree on the name and type of the re­quest ID prop­erty, and they must agree on the name and type of the cor­rel­a­tion ID prop­erty. Like­wise, the re­quest and reply mes­sage formats must define those prop­er­ties or allow them to be added as custom prop­er­ties. For ex­ample, if the re­questor stores the re­quest ID in a first-level XML ele­ment named re­quest_id and the value is an in­teger, the replier has to know this so that it can find the re­quest ID value and pro­cess it prop­erly. The re­quest ID value and cor­rel­a­tion ID value are usu­ally of the same type; if not, the re­questor has to know how the replier will con­vert the re­quest ID to the reply ID.

                                                                                                                                  此模式是异步完成令牌模式 [ POSA2 ] 的更简单的、特定于消息传递的版本。请求者是发起者,回复者是服务,请求者中处理回复的消费者是完成处理程序,消费者用来匹配请求回复的相关标识符是异步完成令牌。

                                                                                                                                  This pat­tern is a sim­pler, mes­saging-spe­cific ver­sion of the Asyn­chron­ous Com­ple­tion Token pat­tern [POSA2]. The re­questor is the Ini­ti­ator, the replier is the Ser­vice, the con­sumer in the re­questor that pro­cesses the reply is the Com­ple­tion Hand­ler, and the Cor­rel­a­tion Iden­ti­fier the con­sumer uses to match the reply to the re­quest is the Asyn­chron­ous Com­ple­tion Token.

                                                                                                                                  相关 ID(以及请求 ID)通常放置在消息的标头中而不是正文中。ID 不是请求者尝试与应答者通信的命令或数据的一部分。事实上,回复者根本没有真正使用该 ID;而是使用了该 ID。它只是保存请求中的 ID 并将其添加到回复中以利于请求者。由于消息正文是在两个系统之间传输的内容,而 ID 不是其中的一部分,因此 ID 位于标头中。

                                                                                                                                  A cor­rel­a­tion ID (and also the re­quest ID) is usu­ally put in the header of a mes­sage rather than in the body. The ID is not part of the com­mand or data the re­questor is trying to com­mu­nic­ate to the replier. In fact, the replier does not really use the ID at all; it just saves the ID from the re­quest and adds it to the reply for the re­questor's be­ne­fit. Since the mes­sage body is the con­tent being trans­mit­ted between the two sys­tems, and the ID is not part of that, the ID goes in the header.

                                                                                                                                  该模式的要点是,回复消息包含一个标记(相关 ID),用于标识相应的请求(通过其请求 ID)。有几种不同的方法可以实现这一目标。

                                                                                                                                  The gist of the pat­tern is that the reply mes­sage con­tains a token (the cor­rel­a­tion ID) that iden­ti­fies the cor­res­pond­ing re­quest (via its re­quest ID). There are sev­eral dif­fer­ent ap­proaches for achiev­ing this.

                                                                                                                                  最简单的方法是每个请求包含一个唯一的 ID,例如消息 ID,并且响应的相关 ID 是该请求的唯一 ID。这将回复与其相应的请求相关联。然而,当请求者尝试处理回复时,了解请求消息通常没有多大帮助。请求者真正想要的是提醒是什么业务任务导致其首先发送请求,以便请求者可以使用回复中的数据完成业务任务。

                                                                                                                                  The simplest ap­proach is for each re­quest to con­tain a unique ID, such as a mes­sage ID, and for the re­sponse's cor­rel­a­tion ID to be the re­quest's unique ID. This relates the reply to its cor­res­pond­ing re­quest. How­ever, when the re­questor is trying to pro­cess the reply, know­ing the re­quest mes­sage often isn't very help­ful. What the re­questor really wants is a re­minder of what busi­ness task caused it to send the re­quest in the first place so that the re­questor can com­plete the busi­ness task using the data in the reply.

                                                                                                                                  业务任务,例如需要执行股票交易或发送采购订单,可能有自己唯一的业务对象标识符(例如订单ID),因此可以使用业务任务的唯一ID作为请求-回复相关 ID。然后,当请求者获得回复及其相关 ID 时,它可以绕过请求消息,直接前往其任务首先引发该请求的业务对象。在这种情况下,请求者和回复者不应使用消息的内置请求消息 ID 和回复相关 ID 属性,而应在请求和回复中使用自定义业务对象 ID 属性,该属性标识此请求的任务的业务对象-回复消息对正在执行。

                                                                                                                                  The busi­ness task, such as need­ing to ex­ecute a stock trade or to ship a pur­chase order, prob­ably has its own unique busi­ness object iden­ti­fier (such as an order ID), so that the busi­ness task's unique ID can be used as the re­quest-reply cor­rel­a­tion ID. Then, when the re­questor gets the reply and its cor­rel­a­tion ID, it can bypass the re­quest mes­sage and go straight to the busi­ness object whose task caused the re­quest in the first place. In this case, rather than use the mes­sages' built-in re­quest mes­sage ID and reply cor­rel­a­tion ID prop­er­ties, the re­questor and replier should use a custom busi­ness object ID prop­erty in both the re­quest and the reply that iden­ti­fies the busi­ness object whose task this re­quest-reply mes­sage pair is per­form­ing.

                                                                                                                                  一种折衷的方法是让请求者保留请求 ID 和业务对象 ID 的映射。当请求者希望将对象 ID 保密时,或者当请求者无法控制回复者的实现并且只能依赖回复者将请求的消息 ID 复制到回复的相关 ID 中时,这尤其有用。在这种情况下,当请求者收到回复时,它会在映射中查找相关 ID 以获取业务对象 ID,然后使用该 ID 来恢复使用回复数据执行业务任务。

                                                                                                                                  A com­prom­ise ap­proach is for the re­questor to keep a map of re­quest IDs and busi­ness object IDs. This is es­pe­cially useful when the re­questor wants to keep the object IDs private or when the re­questor has no con­trol over the replier's im­ple­ment­a­tion and can only depend on the replier copy­ing the re­quest's mes­sage ID into the reply's cor­rel­a­tion ID. In this case, when the re­questor gets the reply, it looks up the cor­rel­a­tion ID in the map to get the busi­ness object ID and then uses that to resume per­form­ing the busi­ness task using the reply data.

                                                                                                                                  消息具有单独的消息 ID 和相关 ID 属性,以便可以链接请求-回复消息对。当一个请求引起回复,而该回复又是另一个请求引起另一个回复,依此类推时,就会发生这种情况。一条消息的消息ID唯一标识它所代表的请求;如果该消息还具有相关 ID,则该消息也是对另一个请求消息的回复,如相关 ID 所标识的。

                                                                                                                                  Mes­sages have sep­ar­ate mes­sage ID and cor­rel­a­tion ID prop­er­ties so that re­quest-reply mes­sage pairs can be chained. This occurs when a re­quest causes a reply, and the reply is in turn an­other re­quest that causes an­other reply, and so on. A mes­sage's mes­sage ID uniquely iden­ti­fies the re­quest it rep­res­ents; if the mes­sage also has a cor­rel­a­tion ID, then the mes­sage is also a reply for an­other re­quest mes­sage, as iden­ti­fied by the cor­rel­a­tion ID.

                                                                                                                                  请求-回复链

                                                                                                                                  Re­quest-Reply Chain­ing

                                                                                                                                  图形/05inf09.gif

                                                                                                                                  仅当应用程序想要追溯从最新回复到原始请求的消息路径时,链接才有用。通常,应用程序想要知道的只是原始请求,无论中间发生了多少个回复步骤。在这种情况下,一旦消息具有非空相关 ID,它就是一个回复,并且由此产生的所有后续回复也应该使用相同的相关 ID。

                                                                                                                                  Chain­ing is only useful if an ap­plic­a­tion wants to re­trace the path of mes­sages from the latest reply back to the ori­ginal re­quest. Often, all the ap­plic­a­tion wants to know is the ori­ginal re­quest, re­gard­less of how many reply steps oc­curred in between. In this situ­ation, once a mes­sage has a non-null cor­rel­a­tion ID, it is a reply, and all sub­se­quent replies that result from it should also use the same cor­rel­a­tion ID.

                                                                                                                                  虽然相关标识符用于将回复与其请求进行匹配,但该请求还可能具有一个返回地址,用于说明将回复放在哪个通道上。相关标识符用于将回复消息与其请求进行匹配,而消息序列的标识符用于指定消息在来自同一发送者的一系列消息中的位置。

                                                                                                                                  While a Cor­rel­a­tion Iden­ti­fier is used to match a reply with its re­quest, the re­quest may also have a Return Ad­dress that states what chan­nel to put the reply on. Whereas a cor­rel­a­tion iden­ti­fier is used to match­ing a reply mes­sage with its re­quest, a Mes­sage Se­quence's iden­ti­fi­ers are used to spe­cify a mes­sage's po­s­i­tion within a series of mes­sages from the same sender.

                                                                                                                                  示例: JMS 相关 ID 属性

                                                                                                                                  Ex­ample: JMS Cor­rel­a­tion-ID Prop­erty

                                                                                                                                  JMS 消息有一个用于关联标识符的预定义属性:JMSCorrelationID ,它通常与另一个预定义属性 JMSMessageID [ JMS 1.1 ] 、 [ Monson -Haefel ]结合使用。 回复消息的相关 ID 根据请求的消息 ID 设置,如下所示:

                                                                                                                                  JMS mes­sages have a pre­defined prop­erty for cor­rel­a­tion iden­ti­fi­ers: JM­SCor­rel­a­tionID, which is typ­ic­ally used in con­junc­tion with an­other pre­defined prop­erty, JMSMes­sageID [JMS 1.1], [Monson-Haefel]. A reply mes­sage's cor­rel­a­tion ID is set from the re­quest's mes­sage ID like this:

                                                                                                                                  Message requestMessage = // 获取请求消息
                                                                                                                                  MessagereplyMessage = // 创建回复消息
                                                                                                                                  String requestID = requestMessage.getJMSMessageID();
                                                                                                                                  replyMessage.setJMSCorrelationID(requestID);
                                                                                                                                  
                                                                                                                                  Mes­sage re­quest­Mes­sage = // Get the re­quest mes­sage
                                                                                                                                  Mes­sage replyMes­sage = // Create the reply mes­sage
                                                                                                                                  String re­questID = re­quest­Mes­sage.getJMSMes­sageID();
                                                                                                                                  replyMes­sage.setJM­SCor­rel­a­tionID(re­questID);
                                                                                                                                  



                                                                                                                                  示例: .NET CorrelationId 属性

                                                                                                                                  Ex­ample: .NET Cor­rel­a­tionId Prop­erty

                                                                                                                                  .NET 中的每个消息都有一个CorrelationId属性,它是确认消息中的一个字符串,通常设置为原始消息的 ID。MessageQueue还具有特殊的查看和接收方法PeekByCorrelationId(string)ReceiveByCorrelationId(string) ,用于查看和使用指定相关 ID 的队列(如果有)上的消息(请参阅选择性消费者)[ SysMsg ]、[ Dickman ] 。

                                                                                                                                  Each Mes­sage in .NET has a Cor­rel­a­tionId prop­erty, a string in an ac­know­ledg­ment mes­sage that is usu­ally set to the ID of the ori­ginal mes­sage. Mes­sageQueue also has spe­cial peek and re­ceive meth­ods, PeekBy­Cor­rel­a­tionId(string) and Re­ceiveBy­Cor­rel­a­tionId(string), for peek­ing at and con­sum­ing the mes­sage on the queue (if any) with the spe­cified cor­rel­a­tion ID (see Se­lect­ive Con­sumer) [SysMsg], [Dick­man].



                                                                                                                                  示例: Web 服务请求-响应

                                                                                                                                  Ex­ample: Web Ser­vices Re­quest-Re­sponse

                                                                                                                                  自 SOAP 1.1 [ SOAP 1.1 ] 起,Web 服务标准并未为异步消息传递提供很好的支持,但 SOAP 1.2 开始对此进行规划。SOAP 1.2 合并了请求-响应消息交换模式[ SOAP 1.2 第 2 部分 ] ,这是异步 SOAP 消息传递的基本部分。然而,请求-响应模式并不强制要求支持“多个正在进行的请求”,因此它没有定义标准的相关标识符字段,甚至没有定义可选字段。

                                                                                                                                  Web ser­vices stand­ards, as of SOAP 1.1 [SOAP 1.1], do not provide very good sup­port for asyn­chron­ous mes­saging, but SOAP 1.2 starts to plan for it. SOAP 1.2 in­cor­por­ates the Re­quest-Re­sponse Mes­sage Ex­change pat­tern [SOAP 1.2 Part 2], a basic part of asyn­chron­ous SOAP mes­saging. How­ever, the re­quest-re­sponse pat­tern does not man­date sup­port for "mul­tiple on­go­ing re­quests," so it does not define a stand­ard Cor­rel­a­tion Iden­ti­fier field, not even an op­tional one.

                                                                                                                                  实际上,服务请求者通常确实需要多个未完成的请求。“Web 服务体系结构使用场景”[ WSAUS ] 讨论了几种不同的异步 Web 服务场景。其中四个请求-响应、远程过程调用(其中传输协议不直接支持[同步]请求-响应)、多个异步响应和异步消息传送使用SOAP标头中的message-id和response-to 字段将响应与它的要求。这是请求-响应示例:

                                                                                                                                  As a prac­tical matter, ser­vice re­questors often do re­quire mul­tiple out­stand­ing re­quests. "Web Ser­vices Ar­chi­tec­ture Usage Scen­arios" [WSAUS] dis­cusses sev­eral dif­fer­ent asyn­chron­ous Web ser­vices scen­arios. Four of them­Re­quest-Re­sponse, Remote Pro­ced­ure Call (where the trans­port pro­tocol does not sup­port [syn­chron­ous] re­quest-re­sponse dir­ectly), Mul­tiple Asyn­chron­ous Re­sponses, and Asyn­chron­ous Mes­sagin­guse mes­sage-id and re­sponse-to fields in the SOAP header to cor­rel­ate a re­sponse to its re­quest. This is the re­quest-re­sponse ex­ample:

                                                                                                                                  包含消息标识符的 SOAP 请求消息
                                                                                                                                  <?xml 版本=“1.0”?>
                                                                                                                                  <env:信封 xmlns:env="http://www.w3.org/2002/06/soap-envelope">
                                                                                                                                    <环境:标头>
                                                                                                                                      <n:MsgHeader xmlns:n="http://example.org/requestresponse">
                                                                                                                                        <n:MessageId>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
                                                                                                                                  图形/ccc.gif:消息ID>
                                                                                                                                      </n:消息头>
                                                                                                                                    </env:标题>
                                                                                                                                    <环境:正文>
                                                                                                                                        …………
                                                                                                                                    </env:正文>
                                                                                                                                  </env:信封>
                                                                                                                                  
                                                                                                                                  <?xml ver­sion="1.0" ?>
                                                                                                                                  <env:En­vel­ope xmlns:env="http://www.w3.org/2002/06/soap-en­vel­ope">
                                                                                                                                    <env:Header>
                                                                                                                                      <n:MsgHeader xmlns:n="http://ex­ample.org/re­questresponse">
                                                                                                                                        <n:Mes­sageId>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
                                                                                                                                  :Mes­sageId>
                                                                                                                                      </n:MsgHeader>
                                                                                                                                    </env:Header>
                                                                                                                                    <env:Body>
                                                                                                                                        ........
                                                                                                                                    </env:Body>
                                                                                                                                  </env:En­vel­ope>
                                                                                                                                  
                                                                                                                                  包含与原始请求相关的 SOAP 响应消息
                                                                                                                                  <?xml 版本=“1.0”?>
                                                                                                                                  <env:信封 xmlns:env="http://www.w3.org/2002/06/soap-envelope">
                                                                                                                                    <环境:标头>
                                                                                                                                      <n:MsgHeader xmlns:n="http://example.org/requestresponse">
                                                                                                                                        <n:MessageId>uuid:09233523-567b-2891-b623-9dke28yod7m9</n
                                                                                                                                  图形/ccc.gif:消息ID>
                                                                                                                                        <n:ResponseTo>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
                                                                                                                                  图形/ccc.gif:回复>
                                                                                                                                      </n:消息头>
                                                                                                                                    </env:标题>
                                                                                                                                    <环境:正文>
                                                                                                                                        …………
                                                                                                                                    </env:正文>
                                                                                                                                  </env:信封>
                                                                                                                                  
                                                                                                                                  <?xml ver­sion="1.0" ?>
                                                                                                                                  <env:En­vel­ope xmlns:env="http://www.w3.org/2002/06/soap-en­vel­ope">
                                                                                                                                    <env:Header>
                                                                                                                                      <n:MsgHeader xmlns:n="http://ex­ample.org/re­questresponse">
                                                                                                                                        <n:Mes­sageId>uuid:09233523-567b-2891-b623-9dke28y­o­d7m9</n
                                                                                                                                  :Mes­sageId>
                                                                                                                                        <n:Re­spon­seTo>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
                                                                                                                                  :Re­spon­seTo>
                                                                                                                                      </n:MsgHeader>
                                                                                                                                    </env:Header>
                                                                                                                                    <env:Body>
                                                                                                                                        ........
                                                                                                                                    </env:Body>
                                                                                                                                  </env:En­vel­ope>
                                                                                                                                  

                                                                                                                                  与JMS和.NET示例一样,在该SOAP示例中,请求消息包含唯一的消息标识符,并且响应消息包含响应(例如,相关ID)字段,其值是请求消息的消息标识符。

                                                                                                                                  Like the JMS and .NET ex­amples, in this SOAP ex­ample, the re­quest mes­sage con­tains a unique mes­sage iden­ti­fier, and the re­sponse mes­sage con­tains a re­sponse (e.g., a cor­rel­a­tion ID) field whose value is the mes­sage iden­ti­fier of the re­quest mes­sage.



                                                                                                                                    消息序列

                                                                                                                                    Message Sequence

                                                                                                                                    图形/messagesequence_icon.gif

                                                                                                                                    我的应用程序需要将大量数据发送到另一个进程,这超出了一条消息所能容纳的范围。或者,我的应用程序发出了一个请求,其回复对于单个消息包含太多数据。

                                                                                                                                    My ap­plic­a­tion needs to send a huge amount of data to an­other pro­cess, more than may fit in a single mes­sage. Or, my ap­plic­a­tion has made a re­quest whose reply con­tains too much data for a single mes­sage.

                                                                                                                                    消息传递如何传输任意大量的数据?

                                                                                                                                    How can mes­saging trans­mit an ar­bit­rar­ily large amount of data?



                                                                                                                                    消息可以任意大,这很好,但单个消息可以容纳的数据量存在实际限制。一些消息传递实现对消息的大小设置了绝对限制。其他实现允许消息变得相当大,但大消息仍然会损害性能。即使消息传递实现允许大消息,消息生产者或消费者也可能会对其一次可以处理的数据量设置限制。例如,许多基于 COBOL 和基于大型机的系统将仅消耗或生成 32 Kb 块的数据。

                                                                                                                                    It's nice to think that mes­sages can be ar­bit­rar­ily large, but there are prac­tical limits to how much data a single mes­sage can hold. Some mes­saging im­ple­ment­a­tions place an ab­so­lute limit on how big a mes­sage can be. Other im­ple­ment­a­tions allow mes­sages to get quite big, but large mes­sages nev­er­the­less hurt per­form­ance. Even if the mes­saging im­ple­ment­a­tion allows large mes­sages, the mes­sage pro­du­cer or con­sumer may place a limit on the amount of data it can pro­cess at once. For ex­ample, many COBOL-based and main­frame-based sys­tems will con­sume or pro­duce data only in 32 Kb chunks.

                                                                                                                                    那么,你如何解决这个问题呢?一种方法是限制您的应用程序,使其永远不需要传输比消息传递层可以在单个消息中存储的数据更多的数据。不过,这是一个任意限制,它可能会阻止您的应用程序产生所需的功能。如果大量数据是请求的结果,则调用者可以发出多个请求,每个请求对应一个结果块,但这会增加网络流量,并且假设调用者甚至知道需要多少个结果块。接收器可以监听数据块,直到没有更多的数据块(但它如何知道没有更多的数据块?),然后尝试找出如何将这些块重新组装成原始的大数据块,但这将是容易出错。

                                                                                                                                    So, how do you get around this? One ap­proach is to limit your ap­plic­a­tion so it never needs to trans­fer more data than the mes­saging layer can store in a single mes­sage. This is an ar­bit­rary limit, though, which can pre­vent your ap­plic­a­tion from pro­du­cing the de­sired func­tion­al­ity. If the large amount of data is the result of a re­quest, the caller could issue mul­tiple re­quests, one for each result chunk, but that in­creases net­work traffic and as­sumes the caller even knows how many result chunks will be needed. The re­ceiver could listen for data chunks until there are no more (but how does it know there aren't any more?) and then try to figure out how to re­as­semble the chunks into the ori­ginal, large piece of data, but that would be error-prone.

                                                                                                                                    灵感来自于邮购公司有时用多个盒子运送订单的方式。如果有三个箱子,发货人会将它们标记为“1 of 3”、“2 of 3”和“3 of 3”,以便收件人知道他收到了哪些箱子以及是否已收到全部箱子。诀窍是将相同的技术应用于消息传递。

                                                                                                                                    In­spir­a­tion comes from the way a mail order com­pany some­times ships an order in mul­tiple boxes. If there are three boxes, the ship­per marks them as "1 of 3," "2 of 3," and "3 of 3," so the re­ceiver knows which ones he has re­ceived and whether he has re­ceived all of them. The trick is to apply the same tech­nique to mes­saging.

                                                                                                                                    每当需要将大量数据分解为消息大小的块时,请将数据作为消息序列发送,并使用序列标识字段标记每个消息。

                                                                                                                                    Whenever a large set of data needs to be broken into mes­sage-size chunks, send the data as a Mes­sage Se­quence and mark each mes­sage with se­quence iden­ti­fic­a­tion fields.

                                                                                                                                    图形/05inf10.gif



                                                                                                                                    三个消息序列标识字段如下。

                                                                                                                                    The three Mes­sage Se­quence iden­ti­fic­a­tion fields are as fol­lows.

                                                                                                                                    1. 序列标识符 将此消息簇与其他消息簇区 分开来。

                                                                                                                                    2. Se­quence iden­ti­fier Dis­tin­guishes this cluster of mes­sages from others.

                                                                                                                                    3. 位置标识符 唯一地标识序列中的每条消息并按顺序对其进行排序。

                                                                                                                                    4. Po­s­i­tion iden­ti­fier Uniquely iden­ti­fies and se­quen­tially orders each mes­sage in a se­quence.

                                                                                                                                    5. 大小 结束指示符 指定群集中的消息数或标记群集中的最后一条消息(其位置标识符随后指定群集的大小)。

                                                                                                                                    6. Size or End in­dic­ator Spe­cifies the number of mes­sages in the cluster or marks the last mes­sage in the cluster (whose po­s­i­tion iden­ti­fier then spe­cifies the size of the cluster).

                                                                                                                                    序列通常被设计为使得序列中的每个消息指示序列的总大小,即该序列中的消息的数量。作为替代方案,您可以设计序列,以便每条消息都指示它是否是该序列中的最终消息。

                                                                                                                                    The se­quences are typ­ic­ally de­signed so that each mes­sage in a se­quence in­dic­ates the total size of the se­quen­cethat is, the number of mes­sages in that se­quence. As an al­tern­at­ive, you can design the se­quences so that each mes­sage in­dic­ates whether it is the final mes­sage in that se­quence.

                                                                                                                                    带结束指示器的消息序列

                                                                                                                                    Mes­sage Se­quence with End In­dic­ator

                                                                                                                                    图形/05inf11.gif

                                                                                                                                    假设一组数据需要作为三个消息的集群发送。三消息簇的序列标识符将是某个唯一的ID。每条消息的位置标识符都不同:1、2 或 3(假设编号从 1 开始,而不是从 0 开始)。如果发送方从一开始就知道消息总数,则每个消息的序列大小为 3。如果发送方直到用完要发送的数据才知道消息总数(例如,发送方正在流式传输数据) ),除了最后一条消息之外的每条消息都会有一个错误的“序列结束”标志。当发送方准备好发送序列中的最后一条消息时,它将将该消息的序列结束标志设置为 true。无论哪种方式,

                                                                                                                                    Let's say a set of data needs to be sent as a cluster of three mes­sages. The se­quence iden­ti­fier of the three-mes­sage cluster will be some unique ID. The po­s­i­tion iden­ti­fier for each mes­sage will be dif­fer­ent: either 1, 2, or 3 (as­sum­ing that num­ber­ing starts from 1, not 0). If the sender knows the total number of mes­sages from the start, the se­quence size for each mes­sage is 3. If the sender does not know the total number of mes­sages until it runs out of data to send (e.g., the sender is stream­ing the data), each mes­sage except the last will have a "se­quence end" flag that is false. When the sender is ready to send the final mes­sage in the se­quence, it will set that mes­sage's se­quence end flag as true. Either way, the po­s­i­tion iden­ti­fi­ers and se­quence size/end in­dic­ator will give the re­ceiver enough in­form­a­tion to re­as­semble the parts back into the whole, even if the parts are not re­ceived in se­quen­tial order.

                                                                                                                                    如果接收者期望一个Message Sequence ,那么发送给它的每条消息都应该作为序列的一部分发送,即使它只是一个序列。否则,当发送不带序列标识字段的单部分消息时,接收方可能会对丢失的字段感到困惑,并可能得出消息无效的结论(请参阅无效消息通道

                                                                                                                                    If the re­ceiver ex­pects a Mes­sage Se­quence, then every mes­sage sent to it should be sent as part of a se­quence, even if it is only a se­quence of one. Oth­er­wise, when a single-part mes­sage is sent without the se­quence iden­ti­fic­a­tion fields, the re­ceiver may become con­fused by the miss­ing fields and may con­clude that the mes­sage is in­valid (see In­valid Mes­sage Chan­nel).

                                                                                                                                    如果接收者按顺序获取了部分消息,但未获取全部消息,则应将其收到的消息重新路由到Invalid Message Channel

                                                                                                                                    If a re­ceiver gets some of the mes­sages in a se­quence but doesn't get all of them, it should reroute the ones it did re­ceive to the In­valid Mes­sage Chan­nel.

                                                                                                                                    应用程序可能希望使用事务客户端用于发送和接收序列。发送者可以使用单个事务按顺序发送所有消息。这样,在所有消息发送完毕之前,不会发送任何消息。同样,接收者可能希望使用单个事务来接收消息,以便在接收到所有消息之前不会真正消耗任何消息。如果序列中的任何消息丢失,接收方可以选择回滚事务,以便稍后可以使用这些消息。在许多消息传递系统实现中,如果在一个事务中发送一系列消息,则将按照发送的顺序接收消息,这简化了接收者将数据重新组合在一起的工作。

                                                                                                                                    An ap­plic­a­tion may wish to use a Trans­ac­tional Client for send­ing and re­ceiv­ing se­quences. The sender can send all of the mes­sages in a se­quence using a single trans­ac­tion. This way, none of the mes­sages will be de­livered until all of them have been sent. Like­wise, a re­ceiver may wish to use a single trans­ac­tion to re­ceive the mes­sages so that it does not truly con­sume any of the mes­sages until it re­ceives all of them. If any of the mes­sages in the se­quence are miss­ing, the re­ceiver can choose to roll back the trans­ac­tion so that the mes­sages can be con­sumed later. In many mes­saging system im­ple­ment­a­tions, if a se­quence of mes­sages is sent in one trans­ac­tion, the mes­sages will be re­ceived in the order they are sent, which sim­pli­fies the re­ceiver's job of put­ting the data back to­gether.

                                                                                                                                    消息序列是请求-回复中的回复消息时,序列标识符和相关标识符通常是相同的东西。如果发送请求的应用程序期望对同一请求有多个响应,并且一个或多个响应可能分为多个部分,则它们将是分开的。当只期望一个响应时,则唯一地标识该响应及其序列是允许的,但是多余的。

                                                                                                                                    When the Mes­sage Se­quence is the reply mes­sage in a Re­quest-Reply, the se­quence iden­ti­fier and the Cor­rel­a­tion Iden­ti­fier are usu­ally the same thing. They would be sep­ar­ate if the ap­plic­a­tion send­ing the re­quest ex­pec­ted mul­tiple re­sponses to the same re­quest, and one or more of the re­sponses could be in mul­tiple parts. When only one re­sponse is ex­pec­ted, then uniquely identi­fy­ing the re­sponse and its se­quence is per­miss­ible but re­dund­ant.

                                                                                                                                    消息序列往往与竞争消费者或消息调度程序不兼容。如果不同的消费者/执行者按顺序接收到不同的消息,则没有一个接收者能够在不相互交换消息内容的情况下重新组装原始数据。因此,消息序列应该通过消息通道与单个消费者一起传输。

                                                                                                                                    Mes­sage Se­quence tends not to be com­pat­ible with Com­pet­ing Con­sumers or Mes­sage Dis­patcher. If dif­fer­ent con­sumers/per­formers re­ceive dif­fer­ent mes­sages in a se­quence, none of the re­ceiv­ers will be able to re­as­semble the ori­ginal data without ex­chan­ging mes­sage con­tents with each other. Thus, a mes­sage se­quence should be trans­mit­ted via a Mes­sage Chan­nel with a single con­sumer.

                                                                                                                                    消息序列的替代方法是使用声明检查。如果应用程序都可以访问公共数据库或文件系统,则无需在两个应用程序之间传输大型文档,而是存储文档并仅在单个消息中传输文档的密钥。

                                                                                                                                    An al­tern­at­ive to Mes­sage Se­quence is to use a Claim Check. Rather than trans­mit­ting a large doc­u­ment between two ap­plic­a­tions, if the ap­plic­a­tions both have access to a common data­base or file system, store the doc­u­ment and just trans­mit a key to the doc­u­ment in a single mes­sage.

                                                                                                                                    使用消息序列类似于使用拆分器将大消息分解为消息序列,并使用聚合器将消息序列重新组合回单个消息。拆分器聚合器使原始消息和最终消息变得非常大,而消息序列使消息端点能够在发送任何消息之前拆分数据,并在接收消息后聚合数据。

                                                                                                                                    Using Mes­sage Se­quence is sim­ilar to using a Split­ter to break up a large mes­sage into a se­quence of mes­sages and using an Ag­greg­ator to re­as­semble the mes­sage se­quence back into a single mes­sage. Split­ter and Ag­greg­ator enable the ori­ginal and final mes­sages to be very large, whereas Mes­sage Se­quence en­ables the Mes­sage En­d­points to split the data before any mes­sages are sent and to ag­greg­ate the data after the mes­sages are re­ceived.

                                                                                                                                    示例: 大文档传输

                                                                                                                                    Ex­ample: Large Doc­u­ment Trans­fer

                                                                                                                                    想象一下,发送者需要向接收者发送一个非常大的文档,该文档太大以至于无法容纳在单个消息中,或者一次发送所有文档是不切实际的。在这种情况下,应该将文档分成多个部分,并且每个部分都可以作为消息发送。每条消息都需要指示它在序列中的位置以及总共有多少条消息。例如,MSMQ 消息的最大大小为 4 MB。[ Dickman ]讨论了如何在 MSMQ 中发送多部分消息序列。

                                                                                                                                    Ima­gine that a sender needs to send a re­ceiver an ex­tremely large doc­u­ment, so large that it will not fit within a single mes­sage or is im­prac­tical to send all at once. In this case, the doc­u­ment should be broken into parts, and each part can be sent as a mes­sage. Each mes­sage needs to in­dic­ate its po­s­i­tion in the se­quence and how many mes­sages there are in all. For ex­ample, the max­imum size of an MSMQ mes­sage is 4 MB. [Dick­man] dis­cusses how to send a mul­ti­part mes­sage se­quence in MSMQ.



                                                                                                                                    示例: 多项目查询

                                                                                                                                    Ex­ample: Multi-Item Query

                                                                                                                                    考虑一个请求特定作者的所有书籍列表的查询。由于这可能是一个非常大的列表,因此消息传递设计可能会选择将每个匹配项作为单独的消息返回。然后,每条消息都需要指示该回复所针对的查询、消息在序列中的位置以及预期的消息数量。

                                                                                                                                    Con­sider a query that re­quests a list of all books by a cer­tain author. Be­cause this could be a very large list, the mes­saging design might choose to return each match as a sep­ar­ate mes­sage. Then, each mes­sage needs to in­dic­ate the query this reply is for, the mes­sage's po­s­i­tion in the se­quence, and how many mes­sages to expect.



                                                                                                                                    示例: 分布式查询

                                                                                                                                    Ex­ample: Dis­trib­uted Query

                                                                                                                                    考虑由多个接收者部分执行的查询。如果部件有某种顺序,则需要在回复消息中指出,以便可以正确组装完整的回复。每个接收者都需要知道其在整个顺序中的位置,并且需要指示该位置是回复的消息序列。

                                                                                                                                    Con­sider a query that is per­formed in parts by mul­tiple re­ceiv­ers. If the parts have some order to them, this will need to be in­dic­ated in the reply mes­sages so that the com­plete reply can be as­sembled prop­erly. Each re­ceiver will need to know its po­s­i­tion in the over­all order and will need to in­dic­ate that po­s­i­tion is the reply's mes­sage se­quence.



                                                                                                                                    示例: JMS 和 .NET

                                                                                                                                    Ex­ample: JMS and .NET

                                                                                                                                    JMS 和 .NET 都没有用于支持消息序列的内置属性。因此,消息传递应用程序必须实现自己的序列字段。在 JMS 中,应用程序可以在标头中定义自己的属性,因此这是一个选项。.NET 不在标头中提供应用程序定义的属性。这些字段也可以在消息正文中定义。请记住,如果序列的接收者需要根据消息的序列来过滤消息,那么如果字段存储在标头中而不是正文中,则这种过滤会更简单。

                                                                                                                                    Neither JMS nor .NET has built-in prop­er­ties for sup­port­ing mes­sage se­quences. There­fore, mes­saging ap­plic­a­tions must im­ple­ment their own se­quence fields. In JMS, an ap­plic­a­tion can define its own prop­er­ties in the header, so that is an option. .NET does not provide ap­plic­a­tion-defined prop­er­ties in the header. The fields could also be defined in the mes­sage body. Keep in mind that if a re­ceiver of the se­quence needs to filter for mes­sages based on their se­quence, such fil­ter­ing is much sim­pler to do if the field is stored in the header rather than in the body.



                                                                                                                                    示例: Web 服务:多个异步响应

                                                                                                                                    Ex­ample: Web Ser­vices: Mul­tiple Asyn­chron­ous Re­sponses

                                                                                                                                    目前,Web 服务标准并未为异步消息传递提供很好的支持,但 W3C 已开始考虑如何提供支持。“Web 服务体系结构使用场景”[ WSAUS ] 讨论了几种不同的异步 Web 服务场景。其中之一是多重异步响应,它使用 SOAP 标头中的message-idresponse-to字段来关联对请求的响应,并使用正文中的序列号total-in-sequence字段来按顺序标识响应。这是多重响应示例:

                                                                                                                                    Web ser­vices stand­ards cur­rently do not provide very good sup­port for asyn­chron­ous mes­saging, but the W3C has star­ted to think about how it could. "Web Ser­vices Ar­chi­tec­ture Usage Scen­arios" [WSAUS] dis­cusses sev­eral dif­fer­ent asyn­chron­ous Web ser­vices scen­arios. One of them­Mul­tiple Asyn­chron­ous Re­sponse­suses mes­sage-id and re­sponse-to fields in the SOAP header to cor­rel­ate a re­sponse to the re­quest, and se­quence-number and total-in-se­quence fields in the body to se­quen­tially identify the re­sponses. This is the mul­tiple re­sponses ex­ample:

                                                                                                                                    包含消息标识符的 SOAP 请求消息
                                                                                                                                    <?xml 版本=“1.0”?>
                                                                                                                                    <env:信封 xmlns:env="http://www.w3.org/2002/06/soap-envelope">
                                                                                                                                      <环境:标头>
                                                                                                                                        <n:MsgHeader xmlns:n="http://example.org/requestresponse">
                                                                                                                                          <n:MessageId>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
                                                                                                                                    图形/ccc.gif:消息ID>
                                                                                                                                        </n:消息头>
                                                                                                                                      </env:标题>
                                                                                                                                      <环境:正文>
                                                                                                                                        …………
                                                                                                                                      </env:正文>
                                                                                                                                    </env:信封>
                                                                                                                                    
                                                                                                                                    <?xml ver­sion="1.0" ?>
                                                                                                                                    <env:En­vel­ope xmlns:env="http://www.w3.org/2002/06/soap-en­vel­ope">
                                                                                                                                      <env:Header>
                                                                                                                                        <n:MsgHeader xmlns:n="http://ex­ample.org/re­questresponse">
                                                                                                                                          <n:Mes­sageId>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
                                                                                                                                    :Mes­sageId>
                                                                                                                                        </n:MsgHeader>
                                                                                                                                      </env:Header>
                                                                                                                                      <env:Body>
                                                                                                                                        ........
                                                                                                                                      </env:Body>
                                                                                                                                    </env:En­vel­ope>
                                                                                                                                    
                                                                                                                                    第一个 SOAP 响应消息包含与原始请求的排序和关联
                                                                                                                                    <?xml 版本=“1.0”?>
                                                                                                                                    <env:信封 xmlns:env="http://www.w3.org/2002/06/soap-envelope">
                                                                                                                                      <环境:标头>
                                                                                                                                        <n:MsgHeader xmlns:n="http://example.org/requestresponse">
                                                                                                                                          <!-- 每个响应消息的 MessageId 都是唯一的 -->
                                                                                                                                          <!-- 每个响应消息的 ResponseTo 都是不变的
                                                                                                                                    图形/ccc.gif按顺序-->
                                                                                                                                          <n:MessageId>uuid:09233523-567b-2891-b623-9dke28yod7m9</n
                                                                                                                                    图形/ccc.gif:消息ID>
                                                                                                                                          <n:ResponseTo>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
                                                                                                                                    图形/ccc.gif:回复>
                                                                                                                                        </n:消息头>
                                                                                                                                        <s:序列 xmlns:s="http://example.org/sequence">
                                                                                                                                          <s:序列号>1</s:序列号>
                                                                                                                                          <s:TotalInSequence>5</s:TotalInSequence>
                                                                                                                                        </s:序列>
                                                                                                                                      </env:标题>
                                                                                                                                      <环境:正文>
                                                                                                                                        …………
                                                                                                                                      </env:正文>
                                                                                                                                    </env:信封>
                                                                                                                                    
                                                                                                                                    <?xml ver­sion="1.0" ?>
                                                                                                                                    <env:En­vel­ope xmlns:env="http://www.w3.org/2002/06/soap-en­vel­ope">
                                                                                                                                      <env:Header>
                                                                                                                                        <n:MsgHeader xmlns:n="http://ex­ample.org/re­questresponse">
                                                                                                                                          <!-- Mes­sageId will be unique for each re­sponse mes­sage -->
                                                                                                                                          <!-- Re­spon­seTo will be con­stant for each re­sponse mes­sage
                                                                                                                                     in the se­quence-->
                                                                                                                                          <n:Mes­sageId>uuid:09233523-567b-2891-b623-9dke28y­o­d7m9</n
                                                                                                                                    :Mes­sageId>
                                                                                                                                          <n:Re­spon­seTo>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
                                                                                                                                    :Re­spon­seTo>
                                                                                                                                        </n:MsgHeader>
                                                                                                                                        <s:Se­quence xmlns:s="http://ex­ample.org/se­quence">
                                                                                                                                          <s:Se­quen­ceNum­ber>1</s:Se­quen­ceNum­ber>
                                                                                                                                          <s:Total­In­Se­quence>5</s:Total­In­Se­quence>
                                                                                                                                        </s:Se­quence>
                                                                                                                                      </env:Header>
                                                                                                                                      <env:Body>
                                                                                                                                        ........
                                                                                                                                      </env:Body>
                                                                                                                                    </env:En­vel­ope>
                                                                                                                                    
                                                                                                                                    最终 SOAP 响应消息包含排序以及与原始请求的关联
                                                                                                                                    <?xml 版本=“1.0”?>
                                                                                                                                    <env:信封 xmlns:env="http://www.w3.org/2002/06/soap-envelope">
                                                                                                                                      <环境:标头>
                                                                                                                                        <n:MsgHeader xmlns:n="http://example.org/requestresponse">
                                                                                                                                          <!-- 每个响应消息的 MessageId 都是唯一的 -->
                                                                                                                                          <!-- 每个响应消息的 ResponseTo 都是不变的
                                                                                                                                    图形/ccc.gif按顺序-->
                                                                                                                                          <n:MessageId>uuid:40195729-sj20-pso3-1092-p20dj28rk104</n
                                                                                                                                    图形/ccc.gif:消息ID>
                                                                                                                                          <n:ResponseTo>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
                                                                                                                                    图形/ccc.gif:回复>
                                                                                                                                        </n:消息头>
                                                                                                                                        <s:序列 xmlns:s="http://example.org/sequence">
                                                                                                                                          <s:序列号>5</s:序列号>
                                                                                                                                          <s:TotalInSequence>5</s:TotalInSequence>
                                                                                                                                        </s:序列>
                                                                                                                                      </env:标题>
                                                                                                                                      <环境:正文>
                                                                                                                                        …………
                                                                                                                                      </env:正文>
                                                                                                                                    </env:信封>
                                                                                                                                    
                                                                                                                                    <?xml ver­sion="1.0" ?>
                                                                                                                                    <env:En­vel­ope xmlns:env="http://www.w3.org/2002/06/soap-en­vel­ope">
                                                                                                                                      <env:Header>
                                                                                                                                        <n:MsgHeader xmlns:n="http://ex­ample.org/re­questresponse">
                                                                                                                                          <!-- Mes­sageId will be unique for each re­sponse mes­sage -->
                                                                                                                                          <!-- Re­spon­seTo will be con­stant for each re­sponse mes­sage
                                                                                                                                     in the se­quence-->
                                                                                                                                          <n:Mes­sageId>uuid:40195729-sj20-pso3-1092-p20d­j28rk104</n
                                                                                                                                    :Mes­sageId>
                                                                                                                                          <n:Re­spon­seTo>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
                                                                                                                                    :Re­spon­seTo>
                                                                                                                                        </n:MsgHeader>
                                                                                                                                        <s:Se­quence xmlns:s="http://ex­ample.org/se­quence">
                                                                                                                                          <s:Se­quen­ceNum­ber>5</s:Se­quen­ceNum­ber>
                                                                                                                                          <s:Total­In­Se­quence>5</s:Total­In­Se­quence>
                                                                                                                                        </s:Se­quence>
                                                                                                                                      </env:Header>
                                                                                                                                      <env:Body>
                                                                                                                                        ........
                                                                                                                                      </env:Body>
                                                                                                                                    </env:En­vel­ope>
                                                                                                                                    

                                                                                                                                    标头中的消息ID用作响应中的序列标识符。每个响应中的sequence-number和total-in-sequence分别是位置标识符和大小指示符。

                                                                                                                                    The mes­sage-id in the header is used as the se­quence iden­ti­fier in the re­sponses. The se­quence-number and total-in-se­quence in each re­sponse are a po­s­i­tion iden­ti­fier and a size in­dic­ator re­spect­ively.



                                                                                                                                      消息过期

                                                                                                                                      Message Expiration

                                                                                                                                      图形/messageexpiration_icon.gif

                                                                                                                                      我的应用程序正在使用消息传递。如果在一定时间内未收到消息数据或请求,则该消息是无用的,应被忽略。

                                                                                                                                      My ap­plic­a­tion is using Mes­saging. If a Mes­sages data or re­quest is not re­ceived by a cer­tain time, it is use­less and should be ig­nored.

                                                                                                                                      发件人如何指示消息何时应被视为过时,从而不应被处理?

                                                                                                                                      How can a sender in­dic­ate when a mes­sage should be con­sidered stale and thus shouldn't be pro­cessed?



                                                                                                                                      消息传递实际上保证了消息最终将传递给接收者。它无法保证交货可能需要多长时间。例如,如果连接发送者和接收者的网络停机一周,则可能需要一周的时间才能传递消息。即使参与者(发送者、网络和接收者)不可靠,消息传递也是高度可靠的,但在不可靠的情况下消息传输​​可能需要很长时间。(有关更多详细信息,请参阅保证交付。

                                                                                                                                      Mes­saging prac­tic­ally guar­an­tees that the Mes­sage will even­tu­ally be de­livered to the re­ceiver. What it cannot guar­an­tee is how long the de­liv­ery may take. For ex­ample, if the net­work con­nect­ing the sender and re­ceiver is down for a week, then it could take a week to de­liver a mes­sage. Mes­saging is highly re­li­able, even when the par­ti­cipants (sender, net­work, and re­ceiver) are not, but mes­sages can take a very long time to trans­mit in un­re­li­able cir­cum­stances. (For more de­tails, see Guar­an­teed De­liv­ery.)

                                                                                                                                      通常,消息内容的有用时间有实际限制。如果发出股票报价请求的呼叫者在一分钟左右没有收到答复,则可能会失去兴趣。这意味着请求的传输时间不应超过一分钟,而且答案最好很快传回。超过一两分钟的股票报价回复可能太旧了,因此无关紧要。

                                                                                                                                      Often, a mes­sage's con­tents have a prac­tical limit for how long they're useful. A caller is­su­ing a stock quote re­quest prob­ably loses in­terest if it does not re­ceive an answer within a minute or so. That means the re­quest should not take more than a minute to trans­mit but also that the answer had better trans­mit back very quickly. A stock quote reply more than a minute or two old is prob­ably too old and there­fore ir­rel­ev­ant.

                                                                                                                                      一旦发送者发送消息而没有得到回复,则无法取消或撤回该消息。同样,接收者可以检查消息何时发送,如果消息太旧则拒绝该消息,但是不同情况下的不同发送者对于多长才算太长可能有不同的想法,那么接收者如何知道要拒绝哪些消息呢?发送者需要一种指定消息生命周期的方法。

                                                                                                                                      Once the sender sends a mes­sage and does not get a reply, it has no way to cancel or recall the mes­sage. Like­wise, a re­ceiver could check when a mes­sage was sent and reject the mes­sage if it's too old, but dif­fer­ent senders under dif­fer­ent cir­cum­stances may have dif­fer­ent ideas about how long is too long, so how does the re­ceiver know which mes­sages to reject? What is needed is a way for the sender to spe­cify the mes­sage's life­time.

                                                                                                                                      设置消息过期时间以指定消息有效的时间限制。

                                                                                                                                      Set the Mes­sage Ex­pir­a­tion to spe­cify a time limit for how long the mes­sage is viable.

                                                                                                                                      图形/05inf12.gif



                                                                                                                                      一旦消息的有效时间过去,并且该消息仍未被消费,则该消息将过期。消息系统的消费者将忽略过期的消息;他们对待该消息就好像它从未发送过一样。大多数消息系统实现将过期消息重新路由到死信通道,而其他系统则简单地丢弃过期消息;这可能是可配置的。

                                                                                                                                      Once the time for which a mes­sage is viable passes, and the mes­sage still has not been con­sumed, then the mes­sage will expire. The mes­saging system's con­sumers will ignore an ex­pired mes­sage; they treat the mes­sage as if it where never sent in the first place. Most mes­saging system im­ple­ment­a­tions reroute ex­pired mes­sages to the Dead Letter Chan­nel, whereas others simply dis­card ex­pired mes­sages; this may be con­fig­ur­able.

                                                                                                                                      消息过期就像牛奶盒上的过期日期。在那之后,你就不应该喝牛奶了。同样,当消息过期时,消息传递系统不应再传递它。如果接收者仍然收到消息但无法在过期之前处理它,则接收者应该丢弃该消息。

                                                                                                                                      A Mes­sage Ex­pir­a­tion is like the ex­pir­a­tion date on a milk carton. After that date, you shouldn't drink the milk. Like­wise, when a mes­sage ex­pires, the mes­saging system should no longer de­liver it. If a re­ceiver still re­ceives a mes­sage but cannot pro­cess it before the ex­pir­a­tion, the re­ceiver should throw away the mes­sage.

                                                                                                                                      消息过期是一个时间戳(日期和时间),指定消息的生存时间或过期时间。该设置可以用相对或绝对术语来指定。绝对设置指定消息过期的日期和时间。相对设置指定消息在过期之前应存活多长时间;消息系统会根据消息发送的时间将相对设置转换为绝对设置。消息传递系统负责调整与发送者不同时区的接收者的时间戳、调整夏令时以及任何其他可能导致两个不同时钟无法就时间达成一致的问题。

                                                                                                                                      A Mes­sage Ex­pir­a­tion is a timestamp (date and time) that spe­cifies how long the mes­sage will live or when it will expire. The set­ting can be spe­cified in re­l­at­ive or ab­so­lute terms. An ab­so­lute set­ting spe­cifies a date and time when the mes­sage will expire. A re­l­at­ive set­ting spe­cifies how long the mes­sage should live before it ex­pires; the mes­saging system will use the time when the mes­sage is sent to con­vert the re­l­at­ive set­ting into an ab­so­lute one. The mes­saging system is re­spons­ible for ad­just­ing the timestamp for re­ceiv­ers in dif­fer­ent time zones from the sender, for ad­just­ments in day­light sav­ings times, and any other issues that can keep two dif­fer­ent clocks from agree­ing on what time it is.

                                                                                                                                      消息过期属性有一个相关属性,发送时间,它指定消息的发送时间。消息的绝对过期时间戳必须晚于其发送时间戳(否则消息将立即过期)。为了避免这个问题,发送者通常指定相对的过期时间,在这种情况下,消息系统通过将相对超时时间添加到发送时间戳来计算过期时间戳(过期时间=发送时间+生存时间)。

                                                                                                                                      The mes­sage ex­pir­a­tion prop­erty has a re­lated prop­erty, sent time, which spe­cifies when the mes­sage was sent. A mes­sage's ab­so­lute ex­pir­a­tion timestamp must be later than its sent timestamp (or else the mes­sage will expire im­me­di­ately). To avoid this prob­lem, senders usu­ally spe­cify ex­pir­a­tion times re­l­at­ively, in which case the mes­saging system cal­cu­lates the ex­pir­a­tion timestamp by adding the re­l­at­ive timeout to the sent timestamp (ex­pir­a­tion time = sent time + time to live).

                                                                                                                                      当消息过期时,消息传递系统可以简单地丢弃它或者可以将其移动到死信通道。发现自己拥有过期消息的接收者应将其移至无效消息通道。通过发布-订阅通道,每个订阅者都会获得自己的消息副本;消息的某些副本可能会成功到达其订阅者,而同一消息的其他副本在其订阅者使用它们之前就过期了。使用请求-答复时,带有过期设置的回复消息可能无法正常工作,如果回复过期,请求的发送者将永远不会知道该请求是否曾经被接收过。如果使用回复过期,则请求发送方必须设计为能够处理从未收到预期回复的情况。

                                                                                                                                      When a mes­sage ex­pires, the mes­saging system may simply dis­card it or may move it to a Dead Letter Chan­nel. A re­ceiver that finds itself in pos­ses­sion of an ex­pired mes­sage should move it to the In­valid Mes­sage Chan­nel. With a Pub­lish-Sub­scribe Chan­nel, each sub­scriber gets its own copy of the mes­sage; some copies of a mes­sage may reach their sub­scribers suc­cess­fully, whereas other copies of the same mes­sage expire before their sub­scribers con­sume them. When using Re­quest-Reply, a reply mes­sage with an ex­pir­a­tion set­ting may not work wellif the reply ex­pires, the sender of the re­quest will never know whether the re­quest was ever re­ceived in the first place. If reply ex­pir­a­tions are used, the re­quest sender has to be de­signed to handle the case where ex­pec­ted replies are never re­ceived.

                                                                                                                                      示例: JMS 生存时间参数

                                                                                                                                      Ex­ample: JMS Time-to-Live Para­meter

                                                                                                                                      消息过期是 JMS 规范所说的“消息生存时间”[ JMS 1.1 ]、[ Hapner ]。JMS 消息具有用于消息过期的预定义属性JMSExpiration ,但发送方不应通过Message.setJMSExpiration(long) 设置它,因为JMS 提供程序将在发送消息时覆盖该设置。相反,发送者应该使用其MessageProducer( QueueSender或TopicPublisher )来设置其发送的所有消息的超时;此设置的方法是MessageProducer.setTimeToLive(long)发送者还可以使用MessageProducer.send(Message message, int DeliveryMode, intpriority, long timeToLive)方法设置单个消息的生存时间,其中第四个参数是以毫秒为单位的生存时间。生存时间是一个相对设置,指定消息发送后多长时间应过期。

                                                                                                                                      Mes­sage ex­pir­a­tion is what the JMS spe­cific­a­tion calls "mes­sage time-to-live" [JMS 1.1], [Hapner]. JMS mes­sages have a pre­defined prop­erty for mes­sage ex­pir­a­tion, JM­S­Ex­pir­a­tion, but a sender should not set it via Mes­sage.setJM­S­Ex­pir­a­tion(long) be­cause the JMS pro­vider will over­ride that set­ting when the mes­sage is sent. Rather, the sender should use its Mes­sage­Pro­du­cer (QueueSender or Top­icPub­lisher) to set the timeout for all mes­sages it sends; the method for this set­ting is Mes­sage­Pro­du­cer.set­Ti­meT­oLive(long). A sender can also set the time-to-live on an in­di­vidual mes­sage using the Mes­sage­Pro­du­cer.send(Mes­sage mes­sage, int de­liv­ery­Mode, int pri­or­ity, long timeToLive) method, where the fourth para­meter is the time-to-live in mil­li­seconds. Time-to-live is a re­l­at­ive set­ting spe­cify­ing how long after the mes­sage is sent it should expire.



                                                                                                                                      示例: .NET 接收时间和到达队列时间属性

                                                                                                                                      Ex­ample: .NET Time-to-Be-Re­ceived and Time-to-Reach-Queue Prop­er­ties

                                                                                                                                      .NET 消息有两个用于指定过期时间的属性:TimeToBeReceivedTimeToReachQueue 。reach-queue 设置指定消息必须到达其目标队列的时间,之后消息可能会无限期地位于队列中。be-received 设置指定接收方必须消耗消息的时间,这限制了将消息传输到目标队列的总时间加上消息在目标队列上可以花费的时间。TimeToBeReceived相当于 JMS 的JMSExpiration属性。两个时间设置都有一个System.TimeSpan类型的值,即时间长度 [ SysMsg],[迪克曼]

                                                                                                                                      A .NET Mes­sage has two prop­er­ties for spe­cify­ing ex­pir­a­tion: TimeToBeRe­ceived and TimeToReachQueue. The reach-queue set­ting spe­cifies how long the mes­sage has to reach its des­tin­a­tion queue, after which the mes­sage might sit in the queue in­def­in­itely. The be-re­ceived set­ting spe­cifies how long the mes­sage has to be con­sumed by a re­ceiver, which limits the total time for trans­mit­ting the mes­sage to its des­tin­a­tion queue plus the amount of time the mes­sage can spend sit­ting on the des­tin­a­tion queue. TimeToBeRe­ceived is equi­val­ent to JMS's JM­S­Ex­pir­a­tion prop­erty. Both time set­tings have a value of type System.TimeSpan, a length of time [SysMsg], [Dick­man].



                                                                                                                                        格式指示器

                                                                                                                                        Format Indicator

                                                                                                                                        通过消息进行通信的多个应用程序遵循商定的数据格式,也许是企业范围的规范数据模型。但是,该格式可能需要随着时间的推移而改变。

                                                                                                                                        Sev­eral ap­plic­a­tions, com­mu­nic­at­ing via Mes­sages, follow an agreed-upon data format, per­haps an en­ter­prise wide Ca­non­ical Data Model. How­ever, that format may need to change over time.

                                                                                                                                        如何设计消息的数据格式以适应未来可能的变化?

                                                                                                                                        How can a mes­sage's data format be de­signed to allow for pos­sible future changes?



                                                                                                                                        即使您设计了适用于所有参与应用程序的数据格式,未来的需求也可能会发生变化。可能会添加具有新格式要求的新应用程序,可能需要将新数据添加到消息中,或者开发人员可能会找到更好的方法来构造相同的数据。不管怎样,设计一个单一的企业数据模型已经够困难的了。设计一个未来永远不需要改变的产品几乎是不可能的。

                                                                                                                                        Even when you design a data format that works for all par­ti­cip­at­ing ap­plic­a­tions, future re­quire­ments may change. New ap­plic­a­tions may be added that have new format re­quire­ments, new data may need to be added to the mes­sages, or de­ve­lopers may find better ways to struc­ture the same data. Whatever the case, design­ing a single en­ter­prise data model is dif­fi­cult enough; design­ing one that will never need to change in the future is darn near im­pos­sible.

                                                                                                                                        当企业的数据格式发生变化时,如果所有的应用程序都随之改变,那是没有问题的。如果每个应用程序都停止使用旧格式并开始使用新格式,并且所有应用程序同时执行此操作,那么转换就会很简单。问题在于,某些应用程序会先于其他应用程序进行转换,而一些较少使用的应用程序可能根本不会转换。即使所有应用程序可以同时转换,也必须消耗所有消息,以便在转换发生之前所有通道都是空的。

                                                                                                                                        When an en­ter­prise's data format changes, there would be no prob­lem if all of the ap­plic­a­tions changed with it. If every ap­plic­a­tion stopped using the old format and star­ted using the new format, and all did so at ex­actly the same time, then con­ver­sion would be simple. The prob­lem is that some ap­plic­a­tions will be con­ver­ted before others, while some less-used ap­plic­a­tions may never be con­ver­ted at all. Even if all ap­plic­a­tions could be con­ver­ted at the same time, all mes­sages would have to be con­sumed so that all chan­nels are empty before the con­ver­sion could occur.

                                                                                                                                        实际上,应用程序必须能够同时支持旧格式和新格式。为此,应用程序必须能够区分哪些消息遵循旧格式,哪些消息遵循新格式。

                                                                                                                                        Real­ist­ic­ally, ap­plic­a­tions will have to be able to sup­port the old format and the new format sim­ul­tan­eously. To do this, ap­plic­a­tions must be able to tell which mes­sages follow the old format and which follow the new.

                                                                                                                                        一种解决方案可能是对新格式的消息使用一组单独的通道。然而,这将导致大量通道、重复设计和配置复杂性,因为每个应用程序都必须针对不断扩展的通道种类进行配置。

                                                                                                                                        One solu­tion might be to use a sep­ar­ate set of chan­nels for the mes­sages with the new format. That, how­ever, would lead to a huge number of chan­nels, du­plic­a­tion of design, and con­fig­ur­a­tion com­plex­ity as each ap­plic­a­tion has to be con­figured for an ever-ex­pand­ing as­sort­ment of chan­nels.

                                                                                                                                        更好的解决方案是让新格式的消息使用旧格式消息所使用的相同通道。这意味着接收者需要一种方法来区分使用同一通道的不同格式的消息。每条消息都必须指定它所使用的格式,并且需要一种简单的方法来指示其格式。

                                                                                                                                        A better solu­tion is for the mes­sages with the new format to use the same chan­nels that the old format mes­sages are using. This means that re­ceiv­ers need a way to dis­tin­guish mes­sages of dif­fer­ent formats that are using the same chan­nel. Each mes­sage must spe­cify what format it is using, and it needs a simple way to in­dic­ate its format.

                                                                                                                                        设计包含格式指示符的数据格式,以便消息指定它正在使用的格式。

                                                                                                                                        Design a data format that in­cludes a Format In­dic­ator so that the mes­sage spe­cifies what format it is using.



                                                                                                                                        格式指示符使发送者能够告诉接收者消息的格式。这样,期望多种可能格式的接收者就知道消息正在使用哪一种格式,从而知道如何解释消息的内容。

                                                                                                                                        The Format In­dic­ator en­ables the sender to tell the re­ceiver the format of the mes­sage. This way, a re­ceiver ex­pect­ing sev­eral pos­sible formats knows which one a mes­sage is using and there­fore how to in­ter­pret the mes­sage's con­tents.

                                                                                                                                        实现格式指示器有三种主要替代方案

                                                                                                                                        There are three main al­tern­at­ives for im­ple­ment­ing a Format In­dic­ator:

                                                                                                                                        1. 版本号 唯一标识格式的数字或字符串。发送方和接收方必须就特定指示符指定的格式达成一致。这种方法的优点是发送者和接收者不必就格式描述符的共享存储库达成一致,但缺点是每个人都必须知道指示什么描述符以及在哪里访问它。

                                                                                                                                        2. Ver­sion Number A number or string that uniquely iden­ti­fies the format. Both the sender and re­ceiver must agree on which format is des­ig­nated by a par­tic­u­lar in­dic­ator. The ad­vant­age of this ap­proach is that the sender and re­ceiver do not have to agree on a shared re­pos­it­ory for format descriptors, but the draw­back is that each must know what descriptor is in­dic­ated and where to access it.

                                                                                                                                        3. 外键 唯一的 ID,例如文件名、数据库行键、主键或指定格式文档的 Internet URL。发送者和接收者必须就键到文档的映射以及模式文档的格式达成一致。这种方法的优点是外键非常紧凑,可以指向共享存储库中的详细数据格式描述。主要缺点在于每个消息传递参与者必须从潜在的远程资源检索格式文档。

                                                                                                                                        4. For­eign Key A unique IDsuch as a fi­le­name, a data­base row key, a home primary key, or an In­ter­net URLthat spe­cifies a format doc­u­ment. The sender and re­ceiver must agree on the map­ping of keys to doc­u­ments and the format of the schema doc­u­ment. The ad­vant­age of this ap­proach is that the for­eign key is very com­pact and can point to a de­tailed data format de­scrip­tion in a shared re­pos­it­ory. The main draw­back lies in the fact that each mes­saging par­ti­cipant has to re­trieve the format doc­u­ment from a po­ten­tially remote re­source.

                                                                                                                                        5. 格式文档 描述数据格式的 模式。架构文档不必通过外键检索或从版本号推断;它嵌入在消息中。发送者和接收者必须就模式的格式达成一致。这种替代方案的优点是消息是独立的。然而,消息流量增加,因为每条消息都携带很少改变的格式信息。

                                                                                                                                        6. Format Doc­u­ment A schema that de­scribes the data format. The schema doc­u­ment does not have to be re­trieved via a for­eign key or in­ferred from a ver­sion number; it is em­bed­ded in the mes­sage. The sender and the re­ceiver must agree on the format of the schema. The ad­vant­age of this al­tern­at­ive is that mes­sages are self-con­tained. How­ever, mes­sage traffic in­creases be­cause each mes­sage car­ries format in­form­a­tion that rarely changes.

                                                                                                                                        版本号或外键可以存储在发送者和接收者同意的标头字段中。对格式版本不感兴趣的接收者可以忽略该字段。格式文档可能太长或太复杂,无法存储在标头字段中,在这种情况下,消息正文必须具有包含两部分的格式:架构和数据。

                                                                                                                                        A ver­sion number or for­eign key can be stored in a header field that the senders and re­ceiv­ers agree upon. Re­ceiv­ers that are not in­ter­ested in the format ver­sion can ignore the field. A format doc­u­ment may be too long or com­plex to store in a header field, in which case the mes­sage body must have a format that con­tains two parts: the schema and the data.

                                                                                                                                        示例: XML

                                                                                                                                        Ex­ample: XML

                                                                                                                                        XML 文档提供了所有三种方法的示例。一个示例是 XML 声明,如下所示:

                                                                                                                                        XML doc­u­ments have ex­amples of all three ap­proaches. One ex­ample is an XML de­clar­a­tion, like this:

                                                                                                                                        <?xml 版本=“1.0”?>
                                                                                                                                        
                                                                                                                                        <?xml ver­sion="1.0"?>
                                                                                                                                        

                                                                                                                                        这里,1.0是版本号,指示文档是否符合该版本的 XML 规范。另一个例子是文档类型声明,它可以采用两种形式。它可以是包含系统标识符的外部 ID,如下所示:

                                                                                                                                        Here, 1.0 is a ver­sion number that in­dic­ates the doc­u­ment's con­form­ance to that ver­sion of the XML spe­cific­a­tion. An­other ex­ample is the doc­u­ment type de­clar­a­tion, which can take two forms. It can be an ex­ternal ID con­tain­ing a system iden­ti­fier, like this:

                                                                                                                                        <!DOCTYPE问候系统“hello.dtd”>
                                                                                                                                        
                                                                                                                                        <!DOC­TYPE greet­ing SYSTEM "hello.dtd">
                                                                                                                                        

                                                                                                                                        系统标识符hello.dtd是一个外键,指示包含描述此 XML 文档格式的 DTD 文档的文件。该声明也可以包含在本地,如下所示:

                                                                                                                                        The system iden­ti­fier, hello.dtd, is a for­eign key that in­dic­ates the file con­tain­ing the DTD doc­u­ment that de­scribes this XML doc­u­ment's format. The de­clar­a­tion can also be in­cluded loc­ally, like this:

                                                                                                                                        <!DOCTYPE 问候语 [
                                                                                                                                          <!元素问候语 (#PCDATA)>
                                                                                                                                        ]>
                                                                                                                                        
                                                                                                                                        <!DOC­TYPE greet­ing [
                                                                                                                                          <!ELE­MENT greet­ing (#PCDATA)>
                                                                                                                                        ]>
                                                                                                                                        

                                                                                                                                        标记声明[<!ELEMENTgreeting (#PCDATA)>]是一个格式文档,是一个描述 XML 格式 [ XML 1.0 ] 的嵌入式架构文档。

                                                                                                                                        The markup de­clar­a­tion, [<!ELE­MENT greet­ing (#PCDATA)>], is a format doc­u­ment, an em­bed­ded schema doc­u­ment that de­scribes the XML's format [XML 1.0].



                                                                                                                                          介绍

                                                                                                                                          Introduction

                                                                                                                                          到目前为止,我们已经介绍了很多模式。我们已经了解了基本的消息传递组件,例如Message Channel 、 Message和Message Endpoint 。我们还看到了消息传递通道和消息构建的详细模式。那么,所有这些模式如何组合在一起呢?开发人员如何使用这些模式集成应用程序?代码是什么样的,它是如何工作的?

                                                                                                                                          So far, we've in­tro­duced a lot of pat­terns. We've seen the basic mes­saging com­pon­ents, such as Mes­sage Chan­nel, Mes­sage, and Mes­sage En­d­point. We've also seen de­tailed pat­terns for mes­saging chan­nels and for mes­sage con­struc­tion. So, how do all of these pat­terns fit to­gether? How does a de­ve­loper in­teg­rate ap­plic­a­tions using these pat­terns? What does the code look like, and how does it work?

                                                                                                                                          这是我们真正看到代码的章节。我们有两个例子:

                                                                                                                                          This is the chapter where we really get to see the code. We have two ex­amples:

                                                                                                                                          • 请求-回复 演示(在 Java 和 .NET/C# 中)如何使用消息传递发送请求消息并使用回复消息进行响应。

                                                                                                                                          • Re­quest-Reply Demon­strates (in Java and .NET/C#) how to use mes­saging to send a re­quest mes­sage and re­spond with a reply mes­sage.

                                                                                                                                          • 发布-订阅 探索如何使用 JMS 主题来实现观察者 [ GoF ]模式。

                                                                                                                                          • Pub­lish-Sub­scribe Ex­plores how to use a JMS Topic to im­ple­ment the Ob­server [GoF] pat­tern.

                                                                                                                                          这两个简单的示例应该可以帮助您开始向自己的应用程序添加消息传递。

                                                                                                                                          These two simple ex­amples should get you star­ted on adding mes­saging to your own ap­plic­a­tions.

                                                                                                                                          请求-回复示例

                                                                                                                                          Re­quest-Reply Ex­ample

                                                                                                                                          这是一个简单但功能强大的示例,传输请求并传回回复。它由两个主要类组成:

                                                                                                                                          This is a simple but power­ful ex­ample, trans­mit­ting a re­quest and trans­mit­ting back a reply. It con­sists of two main classes:

                                                                                                                                          • 请求者发送请求消息并期望接收回复消息的对象。

                                                                                                                                          • Re­questor The object that sends the re­quest mes­sage and ex­pects to re­ceive the reply mes­sage.

                                                                                                                                          • Replier 接收请求消息并发送回复消息作为响应的对象。

                                                                                                                                          • Replier The object that re­ceives the re­quest mes­sage and sends a reply mes­sage in re­sponse.

                                                                                                                                          这两个发送简单消息的简单类说明了多种模式:

                                                                                                                                          These two simple classes send­ing simple mes­sages il­lus­trate a number of the pat­terns:

                                                                                                                                          示例代码还演示了第 10 章“消息传递端点”中的一些模式:

                                                                                                                                          The ex­ample code also demon­strates a couple of pat­terns from Chapter 10, "Mes­saging En­d­points":

                                                                                                                                          虽然本书是技术、产品和语言中立的,但代码却不能,所以我们选择了两个消息传递编程平台来实现这个示例:

                                                                                                                                          While this book is tech­no­logy-, product-, and lan­guage-neut­ral, code cannot be, so we've chosen two mes­saging pro­gram­ming plat­forms to im­ple­ment this ex­ample:

                                                                                                                                          • Java J2EE 中的 JMS API

                                                                                                                                          • The JMS API in Java J2EE

                                                                                                                                          • Microsoft .NET 中使用 C# 的 MSMQ API

                                                                                                                                          • The MSMQ API in Mi­crosoft .NET using C#

                                                                                                                                          两个平台都实现了相同的请求-应答示例。选择您最喜欢的平台作为消息传递工作原理的示例。如果您想了解消息传递在其他平台上的工作原理,但不知道如何为该平台编写代码,您可以通过将其与您已知的语言中的代码进行比较来找出答案。

                                                                                                                                          The same re­quest-reply ex­ample is im­ple­men­ted in both plat­forms. Choose your fa­vor­ite plat­form as an ex­ample of how mes­saging works. If you'd like to see how mes­saging works on the other plat­form but don't know how to write code for that plat­form, you can figure it out by com­par­ing it to the code in the lan­guage you already know.

                                                                                                                                          发布-订阅示例

                                                                                                                                          Pub­lish-Sub­scribe Ex­ample

                                                                                                                                          此示例探讨如何使用发布-订阅通道实现观察者模式。 它考虑了分发和线程问题,并讨论了消息传递如何极大地简化这些问题。该示例展示了如何实现通知的推送和拉取模型,并比较了每种模型的结果。它还探讨了如何设计复杂企业所需的一套适当的渠道,该企业有众多主题通知众多观察者。

                                                                                                                                          This ex­ample ex­plores how to im­ple­ment the Ob­server pat­tern using a Pub­lish-Sub­scribe Chan­nel. It con­siders dis­tri­bu­tion and thread­ing issues and dis­cusses how mes­saging greatly sim­pli­fies these issues. The ex­ample shows how to im­ple­ment both the push and pull models of no­ti­fic­a­tion and com­pares the con­se­quences of each. It also ex­plores how to design an ad­equate set of chan­nels needed for a com­plex en­ter­prise with nu­mer­ous sub­jects no­ti­fy­ing nu­mer­ous ob­serv­ers.

                                                                                                                                          讨论和示例代码说明了几种模式:

                                                                                                                                          The dis­cus­sion and sample code il­lus­trate sev­eral pat­terns:

                                                                                                                                          示例代码还演示了第 10 章“消息传递端点”中的一些模式:

                                                                                                                                          The ex­ample code also demon­strates a couple of pat­terns from Chapter 10, "Mes­saging En­d­points":

                                                                                                                                          此示例是使用 JMS 在 Java 中实现的,因为 JMS 通过其 Topic 接口支持发布-订阅通道作为API的显式功能。.NET 不为在 MSMQ 中使用发布-订阅语义提供类似级别的支持。如果确实如此,JMS 示例中的技术也应该很容易适用于 .NET 程序。

                                                                                                                                          This ex­ample is im­ple­men­ted in Java using JMS be­cause JMS sup­ports Pub­lish-Sub­scribe Chan­nel as an ex­pli­cit fea­ture of the API through its Topic in­ter­face. .NET does not provide a sim­ilar level of sup­port for using the pub­lish-sub­scribe se­mantics in MSMQ. When it does, the tech­niques in the JMS ex­ample should be read­ily ap­plic­able to .NET pro­grams as well.

                                                                                                                                            JMS 请求-答复示例

                                                                                                                                            JMS Request-Reply Example

                                                                                                                                            这是如何使用消息传递的简单示例,在 JMS [ JMS ] 中实现。它展示了如何实现Request-Reply,其中请求者应用程序发送请求,回复者应用程序接收请求并返回回复,然后请求者接收回复。它还显示了如何将无效消息重新路由到特殊通道。

                                                                                                                                            This is a simple ex­ample of how to use mes­saging, im­ple­men­ted in JMS [JMS]. It shows how to im­ple­ment Re­quest-Reply, where a re­questor ap­plic­a­tion sends a re­quest, a replier ap­plic­a­tion re­ceives the re­quest and re­turns a reply, and the re­questor re­ceives the reply. It also shows how an in­valid mes­sage will be rerouted to a spe­cial chan­nel.

                                                                                                                                            请求-答复示例的组成部分

                                                                                                                                            Com­pon­ents of the Re­quest-Reply Ex­ample

                                                                                                                                            图形/06inf01.gif

                                                                                                                                            该示例是使用 JMS 1.1 开发的,并使用 J2EE 1.4 参考实现运行。

                                                                                                                                            This ex­ample was de­ve­loped using JMS 1.1 and run using the J2EE 1.4 ref­er­ence im­ple­ment­a­tion.

                                                                                                                                            请求-回复示例

                                                                                                                                            Re­quest-Reply Ex­ample

                                                                                                                                            该示例由两个主要类组成:

                                                                                                                                            This ex­ample con­sists of two main classes:

                                                                                                                                            1. 请求者发送请求消息并等待接收回复消息作为响应的 消息。

                                                                                                                                            2. Re­questor A Mes­sage En­d­point that sends a re­quest mes­sage and waits to re­ceive a reply mes­sage as a re­sponse.

                                                                                                                                            3. Replier等待接收请求消息的消息端点 ;当它出现时,它会通过发送回复消息进行响应。

                                                                                                                                            4. Replier A Mes­sage En­d­point that waits to re­ceive the re­quest mes­sage; when it does, it re­sponds by send­ing the reply mes­sage.

                                                                                                                                            请求者和回复者各自运行在单独的 Java 虚拟机 (JVM) 中,这使得通信变得分布式。

                                                                                                                                            The re­questor and the replier each run in a sep­ar­ate Java Vir­tual Ma­chine (JVM), which is what makes the com­mu­nic­a­tion dis­trib­uted.

                                                                                                                                            此示例假设消息传递系统定义了以下三个队列:

                                                                                                                                            This ex­ample as­sumes that the mes­saging system has these three queues defined:

                                                                                                                                            1. jms/ RequestQueue请求者用来将请求消息发送到回复者的队列。

                                                                                                                                            2. jms/Re­questQueue The queue the re­questor uses to send the re­quest mes­sage to the replier.

                                                                                                                                            3. jms/ReplyQueue 回复者用来向请求者发送回复消息的队列。

                                                                                                                                            4. jms/ReplyQueue The queue the replier uses to send the reply mes­sage to the re­questor.

                                                                                                                                            5. jms/InvalidMessages 请求者和回复者收到无法解释的消息时将消息移至的 队列。

                                                                                                                                            6. jms/In­val­idMes­sages The queue to which the re­questor and replier move a mes­sage when they re­ceive a mes­sage that they cannot in­ter­pret.

                                                                                                                                            该示例的工作原理如下。当请求程序在命令行窗口中启动时,它会启动并打印如下输出:

                                                                                                                                            Here's how the ex­ample works. When the re­questor is star­ted in a com­mand-line window, it starts and prints output like this:

                                                                                                                                            发送请求
                                                                                                                                                    时间:1048261736520 毫秒
                                                                                                                                                    消息ID: ID:_XYZ123_1048261766139_6.2.1.1
                                                                                                                                                    科雷尔。身份证号:空
                                                                                                                                                    回复:com.sun.jms.Queue:jms/ReplyQueue
                                                                                                                                                    内容:世界你好。
                                                                                                                                            
                                                                                                                                            Sent re­quest
                                                                                                                                                    Time:       1048261736520 ms
                                                                                                                                                    Mes­sage ID: ID:_XYZ123_1048261766139_6.2.1.1
                                                                                                                                                    Correl. ID: null
                                                                                                                                                    Reply to:   com.sun.jms.Queue: jms/ReplyQueue
                                                                                                                                                    Con­tents:   Hello world.
                                                                                                                                            

                                                                                                                                            这表明请求者已经发送了请求消息。请注意,即使应答器甚至没有运行并因此无法接收请求,这仍然有效。

                                                                                                                                            This shows that the re­questor has sent a re­quest mes­sage. Notice that this works even though the replier isn't even run­ning and there­fore cannot re­ceive the re­quest.

                                                                                                                                            当回复器在另一个命令行窗口中启动时,它会启动并打印如下输出:

                                                                                                                                            When the replier is star­ted in an­other com­mand-line window, it starts and prints output like this:

                                                                                                                                            收到请求
                                                                                                                                                    时间:1048261766790 毫秒
                                                                                                                                                    消息ID: ID:_XYZ123_1048261766139_6.2.1.1
                                                                                                                                                    科雷尔。身份证号:空
                                                                                                                                                    回复:com.sun.jms.Queue:jms/ReplyQueue
                                                                                                                                                    内容:世界你好。
                                                                                                                                            已发送回复
                                                                                                                                                    时间:1048261766850 毫秒
                                                                                                                                                    消息ID: ID:_XYZ123_1048261758148_5.2.1.1
                                                                                                                                                    科雷尔。ID: ID:_XYZ123_1048261766139_6.2.1.1
                                                                                                                                                    回复:无
                                                                                                                                                    内容:世界你好。
                                                                                                                                            
                                                                                                                                            Re­ceived re­quest
                                                                                                                                                    Time:       1048261766790 ms
                                                                                                                                                    Mes­sage ID: ID:_XYZ123_1048261766139_6.2.1.1
                                                                                                                                                    Correl. ID: null
                                                                                                                                                    Reply to:   com.sun.jms.Queue: jms/ReplyQueue
                                                                                                                                                    Con­tents:   Hello world.
                                                                                                                                            Sent reply
                                                                                                                                                    Time:       1048261766850 ms
                                                                                                                                                    Mes­sage ID: ID:_XYZ123_1048261758148_5.2.1.1
                                                                                                                                                    Correl. ID: ID:_XYZ123_1048261766139_6.2.1.1
                                                                                                                                                    Reply to:   null
                                                                                                                                                    Con­tents:   Hello world.
                                                                                                                                            

                                                                                                                                            这表明回复者收到了请求消息并发送了回复消息。

                                                                                                                                            This shows that the replier re­ceived the re­quest mes­sage and sent a reply mes­sage.

                                                                                                                                            此输出中有几个有趣的项目。首先,注意请求发送和接收的时间戳;请求在发送后被接收(30270 毫秒后)。其次,请注意,这两种情况下的消息 ID 相同,因为它是同一条消息。第三,注意内容Hello world是相同的,这非常好,因为这是正在传输的数据,并且双方必须相同。(此示例中的请求非常蹩脚。它基本上是一条Document Message ;真正的请求通常是一条Command Message 。)第四,名为 jms/ ReplyQueue的队列已在请求消息中指定为回复消息的目的地(退货地址的示例)。

                                                                                                                                            There are sev­eral in­ter­est­ing items in this output. First, notice the re­quest sent and re­ceived timestamps; the re­quest was re­ceived after it was sent (30270 ms later). Second, notice that the mes­sage ID is the same in both cases, be­cause it's the same mes­sage. Third, notice that the con­tents, Hello world, are the same, which is very good be­cause this is the data being trans­mit­ted, and it must be the same on both sides. (The re­quest in this ex­ample is pretty lame. It is ba­sic­ally a Doc­u­ment Mes­sage; a real re­quest would usu­ally be a Com­mand Mes­sage.) Fourth, the queue named jms/ReplyQueue has been spe­cified in the re­quest mes­sage as the des­tin­a­tion for the reply mes­sage (an ex­ample of Return Ad­dress).

                                                                                                                                            接下来,我们将接收请求的输出与发送回复的输出进行比较。首先,请注意,直到收到请求后(60 毫秒后)才发送回复。其次,回复的消息ID与请求的消息ID不同;这是因为请求消息和回复消息是不同的、单独的消息。第三,请求的内容已被提取并添加到回复中(在本示例中,回复器充当简单的“回显”服务)。第四,回复目的地未指定,因为不需要回复(回复不使用Return Address )。第五,回复的相关 ID 与请求的消息 ID 相同(回复确实使用Correlation Identifier )

                                                                                                                                            Next, let's com­pare the output from re­ceiv­ing the re­quest to that for send­ing the reply. First, notice the reply was not sent until after the re­quest was re­ceived (60 ms after). Second, the mes­sage ID for the reply is dif­fer­ent from that for the re­quest; this is be­cause the re­quest and reply mes­sages are dif­fer­ent, sep­ar­ate mes­sages. Third, the con­tents of the re­quest have been ex­trac­ted and added to the reply (in this ex­ample, the replier acts as a simple "echo" ser­vice). Fourth, the reply-to des­tin­a­tion is un­spe­cified be­cause no reply is ex­pec­ted (the reply does not use Return Ad­dress). Fifth, the reply's cor­rel­a­tion ID is the same as the re­quest's mes­sage ID (the reply does use Cor­rel­a­tion Iden­ti­fier).

                                                                                                                                            最后,回到第一个窗口,请求者收到以下回复:

                                                                                                                                            Fi­nally, back in the first window, the re­questor re­ceived the fol­low­ing reply:

                                                                                                                                            收到回复
                                                                                                                                                    时间:1048261797060 毫秒
                                                                                                                                                    消息ID: ID:_XYZ123_1048261758148_5.2.1.1
                                                                                                                                                    科雷尔。ID: ID:_XYZ123_1048261766139_6.2.1.1
                                                                                                                                                    回复:无
                                                                                                                                                    内容:世界你好。
                                                                                                                                            
                                                                                                                                            Re­ceived reply
                                                                                                                                                    Time:       1048261797060 ms
                                                                                                                                                    Mes­sage ID: ID:_XYZ123_1048261758148_5.2.1.1
                                                                                                                                                    Correl. ID: ID:_XYZ123_1048261766139_6.2.1.1
                                                                                                                                                    Reply to:   null
                                                                                                                                                    Con­tents:   Hello world.
                                                                                                                                            

                                                                                                                                            此输出包含几个感兴趣的项目。发送后收到回复(30210 ms)。回复的消息ID在接收时和发送时是相同的,这证明确实是同一条消息。接收到的消息内容与发送的消息内容相同,相关ID告诉请求者这个回复是针对哪个请求的( Correlation Identifier)

                                                                                                                                            This output con­tains sev­eral items of in­terest. The reply was re­ceived after it was sent (30210 ms). The mes­sage ID of the reply was the same when it was re­ceived as when it was sent, which proves that it is indeed the same mes­sage. The mes­sage con­tents re­ceived are the same as those sent, and the cor­rel­a­tion ID tells the re­questor which re­quest this reply is for (Cor­rel­a­tion Iden­ti­fier).

                                                                                                                                            还要注意,请求者被设计为简单地发送请求、接收答复并退出。因此,收到回复后,请求者不再运行。另一方面,应答器不知道何时会收到请求,因此它永远不会停止运行。要停止它,我们进入其命令 shell 窗口并按回车键,这会导致回复程序退出。

                                                                                                                                            Notice too that the re­questor is de­signed to simply send a re­quest, re­ceive a reply, and exit. So, having re­ceived the reply, the re­questor is no longer run­ning. The replier, on the other hand, doesn't know when it might re­ceive a re­quest, so it never stops run­ning. To stop it, we go to its com­mand shell window and press the return key, which causes the replier pro­gram to exit.

                                                                                                                                            这就是 JMS 请求-答复示例。请求者已准备并发送请求。回复者收到请求并发送回复。然后,请求者收到了对其原始请求的答复。

                                                                                                                                            So, that's the JMS re­quest-reply ex­ample. A re­quest was pre­pared and sent by the re­questor. The replier re­ceived the re­quest and sent a reply. Then, the re­questor re­ceived the reply to its ori­ginal re­quest.

                                                                                                                                            请求-回复代码

                                                                                                                                            Re­quest-Reply Code

                                                                                                                                            首先我们看一下请求者是如何实现的:

                                                                                                                                            First, let's take a look at how the re­questor is im­ple­men­ted:

                                                                                                                                            导入 javax.jms.Connection;
                                                                                                                                            导入 javax.jms.Destination;
                                                                                                                                            导入 javax.jms.JMSException;
                                                                                                                                            导入javax.jms.Message;
                                                                                                                                            导入 javax.jms.MessageConsumer;
                                                                                                                                            导入 javax.jms.MessageProducer;
                                                                                                                                            导入javax.jms.Session;
                                                                                                                                            导入 javax.jms.TextMessage;
                                                                                                                                            导入 javax.naming.NamingException;
                                                                                                                                            公共类请求者{
                                                                                                                                            
                                                                                                                                                私人会议;
                                                                                                                                                私有目的地回复队列;
                                                                                                                                                私有 MessageProducer requestProducer;
                                                                                                                                                私人消息消费者回复消费者;
                                                                                                                                                私有 MessageProducer 无效生产者;
                                                                                                                                            
                                                                                                                                                受保护的请求者() {
                                                                                                                                                    极好的();
                                                                                                                                                }
                                                                                                                                            
                                                                                                                                                公共静态请求者newRequestor(连接连接,字符串requestQueueName,
                                                                                                                                                    字符串回复队列名称、字符串无效队列名称)
                                                                                                                                                    抛出 JMSException、NamingException {
                                                                                                                                            
                                                                                                                                                    请求者 请求者 = new Requestor();
                                                                                                                                                    requestor.initialize(连接,requestQueueName,replyQueueName,invalidQueueName);
                                                                                                                                                    退货请求者;
                                                                                                                                                }
                                                                                                                                            
                                                                                                                                                protected void 初始化(连接连接,字符串请求队列名称,
                                                                                                                                                    字符串回复队列名称、字符串无效队列名称)
                                                                                                                                                    抛出 NamingException、JMSException {
                                                                                                                                            
                                                                                                                                                    session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                                                                                                                                            
                                                                                                                                                    目标 requestQueue = JndiUtil.getDestination(requestQueueName);
                                                                                                                                                    回复队列 = JndiUtil.getDestination(replyQueueName);
                                                                                                                                                    目标无效队列 = JndiUtil.getDestination(invalidQueueName);
                                                                                                                                            
                                                                                                                                                    requestProducer = session.createProducer(requestQueue);
                                                                                                                                                    回复消费者 = session.createConsumer(replyQueue);
                                                                                                                                                    invalidProducer = session.createProducer(invalidQueue);
                                                                                                                                                }
                                                                                                                                            
                                                                                                                                                公共无效发送()抛出JMSException {
                                                                                                                                                    TextMessage requestMessage = session.createTextMessage();
                                                                                                                                                    requestMessage.setText("你好世界。");
                                                                                                                                                    requestMessage.setJMSReplyTo(replyQueue);
                                                                                                                                                    requestProducer.send(requestMessage);
                                                                                                                                                    System.out.println("已发送请求");
                                                                                                                                                    System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
                                                                                                                                                    System.out.println("\t消息 ID: " + requestMessage.getJMSMessageID());
                                                                                                                                                    System.out.println("\tCorrel.ID: " + requestMessage.getJMSCorrelationID());
                                                                                                                                                    System.out.println("\t回复:" + requestMessage.getJMSReplyTo());
                                                                                                                                                    System.out.println("\t内容: " + requestMessage.getText());
                                                                                                                                                }
                                                                                                                                            
                                                                                                                                                公共无效 receiveSync() 抛出 JMSException {
                                                                                                                                                    消息msg=replyConsumer.receive();
                                                                                                                                                    if (msg 文本消息实例) {
                                                                                                                                                        TextMessage回复消息 = (TextMessage) msg​​;
                                                                                                                                                        System.out.println("收到回复");
                                                                                                                                                        System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
                                                                                                                                                        System.out.println("\t消息 ID: " +replyMessage.getJMSMessageID());
                                                                                                                                                        System.out.println("\tCorrel.ID: " +replyMessage.getJMSCorrelationID());
                                                                                                                                                        System.out.println("\t回复:" +replyMessage.getJMSReplyTo());
                                                                                                                                                        System.out.println("\t内容: " +replyMessage.getText());
                                                                                                                                                    } 别的 {
                                                                                                                                                        System.out.println("检测到无效消息");
                                                                                                                                                        System.out.println("\tType: " + msg.getClass().getName());
                                                                                                                                                        System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
                                                                                                                                                        System.out.println("\t消息 ID: " + msg.getJMSMessageID());
                                                                                                                                                        System.out.println("\tCorrel.ID: " + msg.getJMSCorrelationID());
                                                                                                                                                        System.out.println("\t回复:" + msg.getJMSReplyTo());
                                                                                                                                            
                                                                                                                                                        msg.setJMSCorrelationID(msg.getJMSMessageID());
                                                                                                                                                        无效生产者.send(msg);
                                                                                                                                            
                                                                                                                                                        System.out.println("发送到无效消息队列");
                                                                                                                                                        System.out.println("\tType: " + msg.getClass().getName());
                                                                                                                                                        System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
                                                                                                                                                        System.out.println("\t消息 ID: " + msg.getJMSMessageID());
                                                                                                                                                        System.out.println("\tCorrel.ID: " + msg.getJMSCorrelationID());
                                                                                                                                                        System.out.println("\t回复:" + msg.getJMSReplyTo());
                                                                                                                                                    }
                                                                                                                                                }
                                                                                                                                            }
                                                                                                                                            
                                                                                                                                            import javax.jms.Con­nec­tion;
                                                                                                                                            import javax.jms.Des­tin­a­tion;
                                                                                                                                            import javax.jms.JM­SEx­cep­tion;
                                                                                                                                            import javax.jms.Mes­sage;
                                                                                                                                            import javax.jms.Mes­sage­Con­sumer;
                                                                                                                                            import javax.jms.Mes­sage­Pro­du­cer;
                                                                                                                                            import javax.jms.Ses­sion;
                                                                                                                                            import javax.jms.Text­Mes­sage;
                                                                                                                                            import javax.naming.NamingEx­cep­tion;
                                                                                                                                            public class Re­questor {
                                                                                                                                            
                                                                                                                                                private Ses­sion ses­sion;
                                                                                                                                                private Des­tin­a­tion replyQueue;
                                                                                                                                                private Mes­sage­Pro­du­cer re­quest­Pro­du­cer;
                                                                                                                                                private Mes­sage­Con­sumer reply­Con­sumer;
                                                                                                                                                private Mes­sage­Pro­du­cer in­val­id­Pro­du­cer;
                                                                                                                                            
                                                                                                                                                pro­tec­ted Re­questor() {
                                                                                                                                                    super();
                                                                                                                                                }
                                                                                                                                            
                                                                                                                                                public static Re­questor ne­wRe­questor(Con­nec­tion con­nec­tion, String re­questQueueName,
                                                                                                                                                    String replyQueueName, String in­val­idQueueName)
                                                                                                                                                    throws JM­SEx­cep­tion, NamingEx­cep­tion {
                                                                                                                                            
                                                                                                                                                    Re­questor re­questor = new Re­questor();
                                                                                                                                                    re­questor.ini­tial­ize(con­nec­tion, re­questQueueName, replyQueueName, in­val­idQueueName);
                                                                                                                                                    return re­questor;
                                                                                                                                                }
                                                                                                                                            
                                                                                                                                                pro­tec­ted void ini­tial­ize(Con­nec­tion con­nec­tion, String re­questQueueName,
                                                                                                                                                    String replyQueueName, String in­val­idQueueName)
                                                                                                                                                    throws NamingEx­cep­tion, JM­SEx­cep­tion {
                                                                                                                                            
                                                                                                                                                    ses­sion = con­nec­tion.cre­ateSes­sion(false, Ses­sion.AUTO_AC­KNOW­LEDGE);
                                                                                                                                            
                                                                                                                                                    Des­tin­a­tion re­questQueue = Jn­di­Util.get­Des­tin­a­tion(re­questQueueName);
                                                                                                                                                    replyQueue = Jn­di­Util.get­Des­tin­a­tion(replyQueueName);
                                                                                                                                                    Des­tin­a­tion in­val­idQueue = Jn­di­Util.get­Des­tin­a­tion(in­val­idQueueName);
                                                                                                                                            
                                                                                                                                                    re­quest­Pro­du­cer = ses­sion.cre­ate­Pro­du­cer(re­questQueue);
                                                                                                                                                    reply­Con­sumer = ses­sion.cre­ate­Con­sumer(replyQueue);
                                                                                                                                                    in­val­id­Pro­du­cer = ses­sion.cre­ate­Pro­du­cer(in­val­idQueue);
                                                                                                                                                }
                                                                                                                                            
                                                                                                                                                public void send() throws JM­SEx­cep­tion {
                                                                                                                                                    Text­Mes­sage re­quest­Mes­sage = ses­sion.cre­at­e­Text­Mes­sage();
                                                                                                                                                    re­quest­Mes­sage.set­Text("Hello world.");
                                                                                                                                                    re­quest­Mes­sage.setJM­S­ReplyTo(replyQueue);
                                                                                                                                                    re­quest­Pro­du­cer.send(re­quest­Mes­sage);
                                                                                                                                                    System.out.println("Sent re­quest");
                                                                                                                                                    System.out.println("\tTime:       " + System.cur­rent­Time­Mil­lis() + " ms");
                                                                                                                                                    System.out.println("\tMes­sage ID: " + re­quest­Mes­sage.getJMSMes­sageID());
                                                                                                                                                    System.out.println("\tCor­rel. ID: " + re­quest­Mes­sage.getJM­SCor­rel­a­tionID());
                                                                                                                                                    System.out.println("\tReply to:   " + re­quest­Mes­sage.getJM­S­ReplyTo());
                                                                                                                                                    System.out.println("\tCon­tents:   " + re­quest­Mes­sage.get­Text());
                                                                                                                                                }
                                                                                                                                            
                                                                                                                                                public void re­ceive­Sync() throws JM­SEx­cep­tion {
                                                                                                                                                    Mes­sage msg = reply­Con­sumer.re­ceive();
                                                                                                                                                    if (msg in­stanceof Text­Mes­sage) {
                                                                                                                                                        Text­Mes­sage replyMes­sage = (Text­Mes­sage) msg;
                                                                                                                                                        System.out.println("Re­ceived reply ");
                                                                                                                                                        System.out.println("\tTime:       " + System.cur­rent­Time­Mil­lis() + " ms");
                                                                                                                                                        System.out.println("\tMes­sage ID: " + replyMes­sage.getJMSMes­sageID());
                                                                                                                                                        System.out.println("\tCor­rel. ID: " + replyMes­sage.getJM­SCor­rel­a­tionID());
                                                                                                                                                        System.out.println("\tReply to:   " + replyMes­sage.getJM­S­ReplyTo());
                                                                                                                                                        System.out.println("\tCon­tents:   " + replyMes­sage.get­Text());
                                                                                                                                                    } else {
                                                                                                                                                        System.out.println("In­valid mes­sage de­tec­ted");
                                                                                                                                                        System.out.println("\tType:       " + msg.get­Class().get­Name());
                                                                                                                                                        System.out.println("\tTime:       " + System.cur­rent­Time­Mil­lis() + " ms");
                                                                                                                                                        System.out.println("\tMes­sage ID: " + msg.getJMSMes­sageID());
                                                                                                                                                        System.out.println("\tCor­rel. ID: " + msg.getJM­SCor­rel­a­tionID());
                                                                                                                                                        System.out.println("\tReply to:   " + msg.getJM­S­ReplyTo());
                                                                                                                                            
                                                                                                                                                        msg.setJM­SCor­rel­a­tionID(msg.getJMSMes­sageID());
                                                                                                                                                        in­val­id­Pro­du­cer.send(msg);
                                                                                                                                            
                                                                                                                                                        System.out.println("Sent to in­valid mes­sage queue");
                                                                                                                                                        System.out.println("\tType:       " + msg.get­Class().get­Name());
                                                                                                                                                        System.out.println("\tTime:       " + System.cur­rent­Time­Mil­lis() + " ms");
                                                                                                                                                        System.out.println("\tMes­sage ID: " + msg.getJMSMes­sageID());
                                                                                                                                                        System.out.println("\tCor­rel. ID: " + msg.getJM­SCor­rel­a­tionID());
                                                                                                                                                        System.out.println("\tReply to:   " + msg.getJM­S­ReplyTo());
                                                                                                                                                    }
                                                                                                                                                }
                                                                                                                                            }
                                                                                                                                            

                                                                                                                                            想要发送请求和接收回复的应用程序可以使用请求者来执行此操作。应用程序为其请求者提供与消息传递系统的连接。它还指定了三个队列的 JNDI 名称:请求队列、回复队列和无效消息队列。这是请求者初始化自身所需的信息。

                                                                                                                                            An ap­plic­a­tion that wants to send re­quests and re­ceive replies could use a re­questor to do so. The ap­plic­a­tion provides its re­questor a con­nec­tion to the mes­saging system. It also spe­cifies the JNDI names of three queues: the re­quest queue, the reply queue, and the in­valid mes­sage queue. This is the in­form­a­tion the re­questor needs to ini­tial­ize itself.

                                                                                                                                            初始化方法中,请求者使用连接和队列名称连接到消息传递系统。

                                                                                                                                            In the ini­tial­ize method, the re­questor uses the con­nec­tion and queue names to con­nect to the mes­saging system.

                                                                                                                                            • 它使用连接来创建会话。应用程序只需要与消息传递系统的一个连接,但应用程序中希望独立发送和接收消息的每个组件都需要自己的会话。两个线程不能共享一个会话;他们应该各自使用不同的会话,以便会话正常工作。

                                                                                                                                            • It uses the con­nec­tion to create a ses­sion. An ap­plic­a­tion needs only one con­nec­tion to a mes­saging system, but each com­pon­ent in the ap­plic­a­tion that wishes to send and re­ceive mes­sages in­de­pend­ently needs its own ses­sion. Two threads cannot share a single ses­sion; they should each use a dif­fer­ent ses­sion so that the ses­sions will work prop­erly.

                                                                                                                                            • 它使用队列名称来查找队列,即Destination 。名称是 JNDI 标识符;JndiUtil执行JNDI 查找。

                                                                                                                                            • It uses the queue names to look up the queues, which are Des­tin­a­tions. The names are JNDI iden­ti­fi­ers; Jn­di­Util per­forms the JNDI look­ups.

                                                                                                                                            • 它创建一个MessageProducer 用于在请求队列上发送消息,一个 MessageConsumer 用于从回复队列接收消息,以及另一个生产者用于将消息移动到无效消息队列。

                                                                                                                                            • It cre­ates a Mes­sage­Pro­du­cer for send­ing mes­sages on the re­quest queue, a Mes­sage­Con­sumer for re­ceiv­ing mes­sages from the reply queue, and an­other pro­du­cer for moving mes­sages to the in­valid mes­sage queue.

                                                                                                                                            请求者必须能够做的一件事是发送请求消息。为此,它实现了send()方法。

                                                                                                                                            One thing that the re­questor must be able to do is send re­quest mes­sages. For that, it im­ple­ments the send() method.

                                                                                                                                            • 它创建一条TextMessage 并将其内容设置为“Hello world”。

                                                                                                                                            • It cre­ates a Text­Mes­sage and sets its con­tents to "Hello world."

                                                                                                                                            • 它将消息的reply-to属性设置为回复队列。这是一个返回地址,它将告诉回复者如何发回回复。

                                                                                                                                            • It sets the mes­sage's reply-to prop­erty to be the reply queue. This is a Return Ad­dress that will tell the replier how to send back the reply.

                                                                                                                                            • 它使用requestProducer 来发送消息。生产者连接到请求队列,因此这是发送消息的队列。

                                                                                                                                            • It uses the re­quest­Pro­du­cer to send the mes­sage. The pro­du­cer is con­nec­ted to the re­quest queue, so that's the queue the mes­sage is sent on.

                                                                                                                                            • 然后它打印出刚刚发送的消息的详细信息。这是在消息发送后完成的,因为消息 ID 由消息传递系统设置,直到消息实际发送后才设置。

                                                                                                                                            • It then prints out the de­tails of the mes­sage it just sent. This is done after the mes­sage is sent be­cause the mes­sage ID is set by the mes­saging system and is not set until the mes­sage is ac­tu­ally sent.

                                                                                                                                            请求者必须能够做的另一件事是接收回复消息。为此,它实现了receiveSync()方法。

                                                                                                                                            The other thing the re­questor must be able to do is re­ceive reply mes­sages. It im­ple­ments the re­ceive­Sync() method for this pur­pose.

                                                                                                                                            • 它使用其replyConsumer 来接收回复。消费者连接到回复队列,因此它将从那里接收消息。它使用receive()方法来获取消息,该消息会同步阻塞,直到消息被传递到队列并从队列中读取,因此请求者是一个Polling Consumer 。由于此接收是同步的,因此请求者的方法称为receiveSync()

                                                                                                                                            • It uses its reply­Con­sumer to re­ceive the reply. The con­sumer is con­nec­ted to the reply queue, so it will re­ceive mes­sages from there. It uses the re­ceive() method to get the mes­sage, which syn­chron­ously blocks until a mes­sage is de­livered to the queue and is read from the queue, so the re­questor is a Polling Con­sumer. Be­cause this re­ceive is syn­chron­ous, the re­questor's method is called re­ceive­Sync().

                                                                                                                                            • 该消息应该是TextMessage 。如果是这样,请求者将获取消息的内容并打印出消息的详细信息。

                                                                                                                                            • The mes­sage should be a Text­Mes­sage. If so, the re­questor gets the mes­sage's con­tents and prints out the mes­sage's de­tails.

                                                                                                                                            • 如果消息不是 TextMessage 则无法处理该消息。请求者不只是丢弃消息,而是将其重新发送到无效消息队列。重新发送消息将更改其消息 ID,因此在重新发送消息之前,请求者将其原始消息 ID 存储在其相关 ID 中(请参阅相关标识符)

                                                                                                                                            • If the mes­sage is not a Text­Mes­sage, then the mes­sage cannot be pro­cessed. Rather than just dis­card­ing the mes­sage, the re­questor re­sends it to the in­valid mes­sage queue. Re­send­ing the mes­sage will change its mes­sage ID, so before re­send­ing it, the re­questor stores its ori­ginal mes­sage ID in its cor­rel­a­tion ID (see Cor­rel­a­tion Iden­ti­fier).

                                                                                                                                            通过这种方式,请求者会执行所有必要的操作来发送请求、接收回复,并在消息没有任何意义时将回复路由到特殊队列。(注意:JMS 提供了一个特殊的类QueueRequestor ,其目的是实现一个接收回复的请求程序,就像我们在这里一样。我们自己实现了代码,而不是使用预构建的 JMS 类,以便我们可以向您展示代码是如何工作的。 )

                                                                                                                                            In this way, a re­questor does everything ne­ces­sary to send a re­quest, re­ceive a reply, and route the reply to a spe­cial queue if the mes­sage does not make any sense. (Note: JMS provides a spe­cial class, QueueRe­questor, whose pur­pose is to im­ple­ment a re­questor that re­ceives replies just as we have here. We im­ple­men­ted the code ourselves rather than using a pre­b­uilt JMS class so that we could show you how the code works.)

                                                                                                                                            接下来我们看看replier是如何实现的。

                                                                                                                                            Next, let's take a look at how the replier is im­ple­men­ted.

                                                                                                                                            导入 javax.jms.Connection;
                                                                                                                                            导入 javax.jms.Destination;
                                                                                                                                            导入 javax.jms.JMSException;
                                                                                                                                            导入javax.jms.Message;
                                                                                                                                            导入 javax.jms.MessageConsumer;
                                                                                                                                            导入 javax.jms.MessageListener;
                                                                                                                                            导入 javax.jms.MessageProducer;
                                                                                                                                            导入javax.jms.Session;
                                                                                                                                            导入 javax.jms.TextMessage;
                                                                                                                                            导入 javax.naming.NamingException;
                                                                                                                                            
                                                                                                                                            公共类 Replier 实现 MessageListener {
                                                                                                                                            
                                                                                                                                                私人会议;
                                                                                                                                                私有 MessageProducer 无效生产者;
                                                                                                                                            
                                                                                                                                                受保护的回复者() {
                                                                                                                                                    极好的();
                                                                                                                                                }
                                                                                                                                            
                                                                                                                                                public static Replier newReplier(连接连接,
                                                                                                                                                  字符串请求队列名称、字符串无效队列名称)
                                                                                                                                                    抛出 JMSException、NamingException {
                                                                                                                                            
                                                                                                                                                    回复者回复者 = new Replier();
                                                                                                                                                    replier.initialize(连接, requestQueueName, invalidQueueName);
                                                                                                                                                    返回回复者;
                                                                                                                                                }
                                                                                                                                            
                                                                                                                                                protected void 初始化(连接连接,字符串请求队列名称,
                                                                                                                                                字符串无效队列名称)
                                                                                                                                                    抛出 NamingException、JMSException {
                                                                                                                                            
                                                                                                                                                    session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                                                                                                                                                    目标 requestQueue = JndiUtil.getDestination(requestQueueName);
                                                                                                                                                    目标无效队列 = JndiUtil.getDestination(invalidQueueName);
                                                                                                                                            
                                                                                                                                                    MessageConsumer requestConsumer = session.createConsumer(requestQueue);
                                                                                                                                                    MessageListener 监听器 = this;
                                                                                                                                                    requestConsumer.setMessageListener(监听器);
                                                                                                                                            
                                                                                                                                                    invalidProducer = session.createProducer(invalidQueue);
                                                                                                                                                }
                                                                                                                                            
                                                                                                                                                公共无效onMessage(消息消息){
                                                                                                                                                    尝试 {
                                                                                                                                                        if ((TextMessage 的消息实例) && (message.getJMSReplyTo() != null)) {
                                                                                                                                                            TextMessage requestMessage = (TextMessage) 消息;
                                                                                                                                                            System.out.println("收到请求");
                                                                                                                                                            System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
                                                                                                                                                            System.out.println("\t消息 ID: " + requestMessage.getJMSMessageID());
                                                                                                                                                            System.out.println("\tCorrel.ID: " + requestMessage.getJMSCorrelationID());
                                                                                                                                                            System.out.println("\t回复:" + requestMessage.getJMSReplyTo());
                                                                                                                                                            System.out.println("\t内容: " + requestMessage.getText());
                                                                                                                                            
                                                                                                                                                            字符串内容 = requestMessage.getText();
                                                                                                                                                            目的地回复Destination = message.getJMSReplyTo();
                                                                                                                                                            MessageProducer 回复生产者 = session.createProducer(replyDestination);
                                                                                                                                            
                                                                                                                                                            TextMessage回复消息 = session.createTextMessage();
                                                                                                                                                            回复消息.setText(内容);
                                                                                                                                                            replyMessage.setJMSCorrelationID(requestMessage.getJMSMessageID());
                                                                                                                                                            回复Producer.send(replyMessage);
                                                                                                                                            
                                                                                                                                                            System.out.println("已发送回复");
                                                                                                                                                            System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
                                                                                                                                                            System.out.println("\t消息 ID: " +replyMessage.getJMSMessageID());
                                                                                                                                                            System.out.println("\tCorrel.ID: " +replyMessage.getJMSCorrelationID());
                                                                                                                                                            System.out.println("\t回复:" +replyMessage.getJMSReplyTo());
                                                                                                                                                            System.out.println("\t内容: " +replyMessage.getText());
                                                                                                                                                        } 别的 {
                                                                                                                                                            System.out.println("检测到无效消息");
                                                                                                                                                            System.out.println("\tType: " + message.getClass().getName());
                                                                                                                                                            System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
                                                                                                                                                            System.out.println("\t消息 ID: " + message.getJMSMessageID());
                                                                                                                                                            System.out.println("\tCorrel.ID: " + message.getJMSCorrelationID());
                                                                                                                                                            System.out.println("\t回复:" + message.getJMSReplyTo());
                                                                                                                                            
                                                                                                                                                            message.setJMSCorrelationID(message.getJMSMessageID());
                                                                                                                                                            invalidProducer.send(消息);
                                                                                                                                            
                                                                                                                                                            System.out.println("发送到无效消息队列");
                                                                                                                                                            System.out.println("\tType: " + message.getClass().getName());
                                                                                                                                                            System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
                                                                                                                                                            System.out.println("\t消息 ID: " + message.getJMSMessageID());
                                                                                                                                                            System.out.println("\tCorrel.ID: " + message.getJMSCorrelationID());
                                                                                                                                                            System.out.println("\t回复:" + message.getJMSReplyTo());
                                                                                                                                                        }
                                                                                                                                                    } catch (JMSException e) {
                                                                                                                                                        e.printStackTrace();
                                                                                                                                                    }
                                                                                                                                                }
                                                                                                                                            }
                                                                                                                                            
                                                                                                                                            import javax.jms.Con­nec­tion;
                                                                                                                                            import javax.jms.Des­tin­a­tion;
                                                                                                                                            import javax.jms.JM­SEx­cep­tion;
                                                                                                                                            import javax.jms.Mes­sage;
                                                                                                                                            import javax.jms.Mes­sage­Con­sumer;
                                                                                                                                            import javax.jms.Mes­sageL­istener;
                                                                                                                                            import javax.jms.Mes­sage­Pro­du­cer;
                                                                                                                                            import javax.jms.Ses­sion;
                                                                                                                                            import javax.jms.Text­Mes­sage;
                                                                                                                                            import javax.naming.NamingEx­cep­tion;
                                                                                                                                            
                                                                                                                                            public class Replier im­ple­ments Mes­sageL­istener {
                                                                                                                                            
                                                                                                                                                private Ses­sion ses­sion;
                                                                                                                                                private Mes­sage­Pro­du­cer in­val­id­Pro­du­cer;
                                                                                                                                            
                                                                                                                                                pro­tec­ted Replier() {
                                                                                                                                                    super();
                                                                                                                                                }
                                                                                                                                            
                                                                                                                                                public static Replier ne­wReplier(Con­nec­tion con­nec­tion,
                                                                                                                                                  String re­questQueueName, String in­val­idQueueName)
                                                                                                                                                    throws JM­SEx­cep­tion, NamingEx­cep­tion {
                                                                                                                                            
                                                                                                                                                    Replier replier = new Replier();
                                                                                                                                                    replier.ini­tial­ize(con­nec­tion, re­questQueueName, in­val­idQueueName);
                                                                                                                                                    return replier;
                                                                                                                                                }
                                                                                                                                            
                                                                                                                                                pro­tec­ted void ini­tial­ize(Con­nec­tion con­nec­tion, String re­questQueueName,
                                                                                                                                                String in­val­idQueueName)
                                                                                                                                                    throws NamingEx­cep­tion, JM­SEx­cep­tion {
                                                                                                                                            
                                                                                                                                                    ses­sion = con­nec­tion.cre­ateSes­sion(false, Ses­sion.AUTO_AC­KNOW­LEDGE);
                                                                                                                                                    Des­tin­a­tion re­questQueue = Jn­di­Util.get­Des­tin­a­tion(re­questQueueName);
                                                                                                                                                    Des­tin­a­tion in­val­idQueue = Jn­di­Util.get­Des­tin­a­tion(in­val­idQueueName);
                                                                                                                                            
                                                                                                                                                    Mes­sage­Con­sumer re­quest­Con­sumer = ses­sion.cre­ate­Con­sumer(re­questQueue);
                                                                                                                                                    Mes­sageL­istener listener = this;
                                                                                                                                                    re­quest­Con­sumer.set­Mes­sageL­istener(listener);
                                                                                                                                            
                                                                                                                                                    in­val­id­Pro­du­cer = ses­sion.cre­ate­Pro­du­cer(in­val­idQueue);
                                                                                                                                                }
                                                                                                                                            
                                                                                                                                                public void on­Mes­sage(Mes­sage mes­sage) {
                                                                                                                                                    try {
                                                                                                                                                        if ((mes­sage in­stanceof Text­Mes­sage) && (mes­sage.getJM­S­ReplyTo() != null)) {
                                                                                                                                                            Text­Mes­sage re­quest­Mes­sage = (Text­Mes­sage) mes­sage;
                                                                                                                                                            System.out.println("Re­ceived re­quest");
                                                                                                                                                            System.out.println("\tTime:       " + System.cur­rent­Time­Mil­lis() + " ms");
                                                                                                                                                            System.out.println("\tMes­sage ID: " + re­quest­Mes­sage.getJMSMes­sageID());
                                                                                                                                                            System.out.println("\tCor­rel. ID: " + re­quest­Mes­sage.getJM­SCor­rel­a­tionID());
                                                                                                                                                            System.out.println("\tReply to:   " + re­quest­Mes­sage.getJM­S­ReplyTo());
                                                                                                                                                            System.out.println("\tCon­tents:   " + re­quest­Mes­sage.get­Text());
                                                                                                                                            
                                                                                                                                                            String con­tents = re­quest­Mes­sage.get­Text();
                                                                                                                                                            Des­tin­a­tion reply­Des­tin­a­tion = mes­sage.getJM­S­ReplyTo();
                                                                                                                                                            Mes­sage­Pro­du­cer replyPro­du­cer = ses­sion.cre­ate­Pro­du­cer(reply­Des­tin­a­tion);
                                                                                                                                            
                                                                                                                                                            Text­Mes­sage replyMes­sage = ses­sion.cre­at­e­Text­Mes­sage();
                                                                                                                                                            replyMes­sage.set­Text(con­tents);
                                                                                                                                                            replyMes­sage.setJM­SCor­rel­a­tionID(re­quest­Mes­sage.getJMSMes­sageID());
                                                                                                                                                            replyPro­du­cer.send(replyMes­sage);
                                                                                                                                            
                                                                                                                                                            System.out.println("Sent reply");
                                                                                                                                                            System.out.println("\tTime:       " + System.cur­rent­Time­Mil­lis() + " ms");
                                                                                                                                                            System.out.println("\tMes­sage ID: " + replyMes­sage.getJMSMes­sageID());
                                                                                                                                                            System.out.println("\tCor­rel. ID: " + replyMes­sage.getJM­SCor­rel­a­tionID());
                                                                                                                                                            System.out.println("\tReply to:   " + replyMes­sage.getJM­S­ReplyTo());
                                                                                                                                                            System.out.println("\tCon­tents:   " + replyMes­sage.get­Text());
                                                                                                                                                        } else {
                                                                                                                                                            System.out.println("In­valid mes­sage de­tec­ted");
                                                                                                                                                            System.out.println("\tType:       " + mes­sage.get­Class().get­Name());
                                                                                                                                                            System.out.println("\tTime:       " + System.cur­rent­Time­Mil­lis() + " ms");
                                                                                                                                                            System.out.println("\tMes­sage ID: " + mes­sage.getJMSMes­sageID());
                                                                                                                                                            System.out.println("\tCor­rel. ID: " + mes­sage.getJM­SCor­rel­a­tionID());
                                                                                                                                                            System.out.println("\tReply to:   " + mes­sage.getJM­S­ReplyTo());
                                                                                                                                            
                                                                                                                                                            mes­sage.setJM­SCor­rel­a­tionID(mes­sage.getJMSMes­sageID());
                                                                                                                                                            in­val­id­Pro­du­cer.send(mes­sage);
                                                                                                                                            
                                                                                                                                                            System.out.println("Sent to in­valid mes­sage queue");
                                                                                                                                                            System.out.println("\tType:       " + mes­sage.get­Class().get­Name());
                                                                                                                                                            System.out.println("\tTime:       " + System.cur­rent­Time­Mil­lis() + " ms");
                                                                                                                                                            System.out.println("\tMes­sage ID: " + mes­sage.getJMSMes­sageID());
                                                                                                                                                            System.out.println("\tCor­rel. ID: " + mes­sage.getJM­SCor­rel­a­tionID());
                                                                                                                                                            System.out.println("\tReply to:   " + mes­sage.getJM­S­ReplyTo());
                                                                                                                                                        }
                                                                                                                                                    } catch (JM­SEx­cep­tion e) {
                                                                                                                                                        e.print­Stack­Trace();
                                                                                                                                                    }
                                                                                                                                                }
                                                                                                                                            }
                                                                                                                                            

                                                                                                                                            应答器是应用程序用来接收请求和发送应答的东西。应用程序向其请求者提供与消息传递系统的连接,以及请求的 JNDI 名称和无效消息队列。(它不需要指定回复队列的名称,因为正如我们将看到的,该名称将由消息的Return Address 提供。 )这是请求者初始化自身所需的信息。

                                                                                                                                            A replier is what an ap­plic­a­tion might use to re­ceive a re­quest and send a reply. The ap­plic­a­tion provides its re­questor a con­nec­tion to the mes­saging system, as well as the JNDI names of the re­quest and in­valid mes­sage queues. (It does not need to spe­cify the name of the reply queue be­cause, as we'll see, that will be provided by the mes­sage's Return Ad­dress.) This is the in­form­a­tion the re­questor needs to ini­tial­ize itself.

                                                                                                                                            回复者的初始化代码与请求者的初始化代码非常相似,但存在一些差异

                                                                                                                                            The replier's ini­tial­ize code is pretty sim­ilar to the re­questor's, but there are a couple of dif­fer­ences.

                                                                                                                                            • 回复者不会查找回复队列并为其创建生产者。这是因为回复者并不认为它总是会在该队列上发送回复;相反,正如我们稍后将看到的,它会让请求消息告诉它在哪个队列上发送回复消息。

                                                                                                                                            • The replier does not look up the reply queue and create a pro­du­cer for it. This is be­cause the replier does not assume it will always send replies on that queue; rather, as we'll see later, it will let the re­quest mes­sage tell it what queue to send the reply mes­sage on.

                                                                                                                                            • 回复者是一个事件驱动的消费者,因此它实现了MessageListener。当消息传递到请求队列时,消息系统会自动调用回复者的onMessage方法。

                                                                                                                                            • The replier is an Event-Driven Con­sumer, so it im­ple­ments Mes­sageL­istener. When a mes­sage is de­livered to the re­quest queue, the mes­saging system will auto­mat­ic­ally call the replier's on­Mes­sage method.

                                                                                                                                            一旦回复者将自己初始化为请求队列上的侦听器,除了等待消息之外,它就没有什么可做的了。与请求者必须显式检查回复队列中的消息不同,回复者是事件驱动的,因此在消息传递系统使用新消息调用其onMessage方法之前不会执行任何操作。该消息将来自请求队列,因为初始化在请求队列上创建了消费者。一旦onMessage收到新消息,它就会像这样处理该消息。

                                                                                                                                            Once the replier has ini­tial­ized itself to be a listener on the re­quest queue, there's not much for it to do but wait for mes­sages. Unlike the re­questor, which has to ex­pli­citly check the reply queue for mes­sages, the replier is event-driven and so does noth­ing until the mes­saging system calls its on­Mes­sage method with a new mes­sage. The mes­sage will be from the re­quest queue be­cause ini­tial­ize cre­ated the con­sumer on the re­quest queue. Once on­Mes­sage re­ceives a new mes­sage, it pro­cesses the mes­sage like this.

                                                                                                                                            • 与请求者处理回复消息一样,请求消息应该是 TextMessage 。 它还应该指定发送回复的队列。如果消息不满足这些要求,则回复者会将消息移至无效消息队列(与请求者相同)。

                                                                                                                                            • As with the re­questor pro­cess­ing a reply mes­sage, the re­quest mes­sage is sup­posed to be a Text­Mes­sage. It is also sup­posed to spe­cify the queue on which to send the reply. If the mes­sage does not meet these re­quire­ments, the replier will move the mes­sage to the in­valid mes­sage queue (same as the re­questor).

                                                                                                                                            • 如果消息满足要求,则回复者将实现其返回地址部分。请记住,请求者设置请求消息的reply-to属性来指定回复队列。回复者现在获取该属性的值并使用它在正确的队列上创建 MessageProducer 。这里重要的部分是回复器没有被硬编码为使用特定的回复队列;它使用每个特定请求消息指定的任何回复队列。

                                                                                                                                            • If the mes­sage meets the re­quire­ments, the replier im­ple­ments its part of Return Ad­dress. Re­mem­ber that the re­questor set the re­quest mes­sage's reply-to prop­erty to spe­cify the reply queue. The replier now gets that prop­erty's value and uses it to create a Mes­sage­Pro­du­cer on the proper queue. The im­port­ant part here is that the replier is not hard-coded to use a par­tic­u­lar reply queue; it uses whatever reply queue each par­tic­u­lar re­quest mes­sage spe­cifies.

                                                                                                                                            • 然后回复者创建回复消息。在此过程中,它通过将回复消息的correlation-id属性设置为与请求消息的message-id属性相同的值来实现相关标识符。

                                                                                                                                            • The replier then cre­ates the reply mes­sage. In doing so, it im­ple­ments Cor­rel­a­tion Iden­ti­fier by set­ting the reply mes­sage's cor­rel­a­tion-id prop­erty to the same value as the re­quest mes­sage's mes­sage-id prop­erty.

                                                                                                                                            • 然后回复者发出回复消息并显示其详细信息。

                                                                                                                                            • The replier then sends out the reply mes­sage and dis­plays its de­tails.

                                                                                                                                            因此,回复者会执行接收消息(可能是请求)并发送回复所需的一切操作。

                                                                                                                                            Thus, a replier does everything ne­ces­sary to re­ceive a mes­sage (pre­sum­ably a re­quest) and send a reply.

                                                                                                                                            无效消息示例

                                                                                                                                            In­valid Mes­sage Ex­ample

                                                                                                                                            在我们讨论这个问题时,让我们看一下Invalid Message Channel的示例。请记住,我们需要的队列之一是名为 jms/InvalidMessages 的队列。 其存在是为了如果 JMS 客户端(消息端点)收到它无法处理的消息,它可以将奇怪的消息移动到特殊通道。

                                                                                                                                            While we're at it, let's look at an ex­ample of In­valid Mes­sage Chan­nel. Re­mem­ber, one of the queues we need is the one named jms/In­val­idMes­sages. This exists so that if a JMS client (a Mes­sage En­d­point) re­ceives a mes­sage it cannot pro­cess, it can move the strange mes­sage to a spe­cial chan­nel.

                                                                                                                                            为了演示无效消息处理,我们设计了一个InvalidMessenger 类。该对象专门用于在请求通道上发送格式不正确的消息。请求通道是数据类型通道,因为请求接收者期望请求采用某种格式。无效的信使只是以不同的格式发送消息;当回复者收到消息时,它无法识别消息的格式,因此会将消息移至无效消息队列。

                                                                                                                                            To demon­strate in­valid mes­sage hand­ling, we have de­signed an In­val­idMes­sen­ger class. This object is spe­cific­ally de­signed to send a mes­sage on the re­quest chan­nel whose format is in­cor­rect. The re­quest chan­nel is a Data­type Chan­nel in that the re­quest re­ceiv­ers expect the re­quests to be in a cer­tain format. The in­valid mes­sen­ger simply sends a mes­sage in a dif­fer­ent format; when the replier re­ceives the mes­sage, it does not re­cog­nize the mes­sage's format, so it moves the mes­sage to the in­valid mes­sage queue.

                                                                                                                                            我们将在一个窗口中运行回复程序,并在另一个窗口中运行无效的信使。当无效的信使发送消息时,它会显示如下输出:

                                                                                                                                            We'll run the replier in one window and the in­valid mes­sen­ger in an­other window. When the in­valid mes­sen­ger sends its mes­sage, it dis­plays output like this:

                                                                                                                                            发送无效消息
                                                                                                                                                    类型:com.sun.jms.ObjectMessageImpl
                                                                                                                                                    时间:1048288516959 毫秒
                                                                                                                                                    消息ID: ID:_XYZ123_1048288516639_7.2.1.1
                                                                                                                                                    科雷尔。身份证号:空
                                                                                                                                                    回复:com.sun.jms.Queue:jms/ReplyQueue
                                                                                                                                            
                                                                                                                                            Sent in­valid mes­sage
                                                                                                                                                    Type:       com.sun.jms.Ob­ject­Mes­sageImpl
                                                                                                                                                    Time:       1048288516959 ms
                                                                                                                                                    Mes­sage ID: ID:_XYZ123_1048288516639_7.2.1.1
                                                                                                                                                    Correl. ID: null
                                                                                                                                                    Reply to:   com.sun.jms.Queue: jms/ReplyQueue
                                                                                                                                            

                                                                                                                                            这表明该消息是 ObjectMessage 的实例而回复者期望的是TextMessage ) 。应答者收到无效消息后,将其重新发送到无效消息队列中。

                                                                                                                                            This shows that the mes­sage is an in­stance of Ob­ject­Mes­sage (whereas the replier is ex­pect­ing a Text­Mes­sage). The replier re­ceives the in­valid mes­sage and re­sends it to the in­valid mes­sage queue.

                                                                                                                                            检测到无效消息
                                                                                                                                                    类型:com.sun.jms.ObjectMessageImpl
                                                                                                                                                    时间:1048288517049 毫秒
                                                                                                                                                    消息ID: ID:_XYZ123_1048288516639_7.2.1.1
                                                                                                                                                    科雷尔。身份证号:空
                                                                                                                                                    回复:com.sun.jms.Queue:jms/ReplyQueue
                                                                                                                                            发送到无效消息队列
                                                                                                                                                    类型:com.sun.jms.ObjectMessageImpl
                                                                                                                                                    时间:1048288517140 毫秒
                                                                                                                                                    消息ID: ID:_XYZ123_1048287020267_6.2.1.2
                                                                                                                                                    科雷尔。ID: ID:_XYZ123_1048288516639_7.2.1.1
                                                                                                                                                    回复:com.sun.jms.Queue:jms/ReplyQueue
                                                                                                                                            
                                                                                                                                            In­valid mes­sage de­tec­ted
                                                                                                                                                    Type:       com.sun.jms.Ob­ject­Mes­sageImpl
                                                                                                                                                    Time:       1048288517049 ms
                                                                                                                                                    Mes­sage ID: ID:_XYZ123_1048288516639_7.2.1.1
                                                                                                                                                    Correl. ID: null
                                                                                                                                                    Reply to:   com.sun.jms.Queue: jms/ReplyQueue
                                                                                                                                            Sent to in­valid mes­sage queue
                                                                                                                                                    Type:       com.sun.jms.Ob­ject­Mes­sageImpl
                                                                                                                                                    Time:       1048288517140 ms
                                                                                                                                                    Mes­sage ID: ID:_XYZ123_1048287020267_6.2.1.2
                                                                                                                                                    Correl. ID: ID:_XYZ123_1048288516639_7.2.1.1
                                                                                                                                                    Reply to:   com.sun.jms.Queue: jms/ReplyQueue
                                                                                                                                            

                                                                                                                                            一个值得注意的见解是,当消息被移动到无效消息队列时,它实际上是在重新发送,因此它会获得一个新的消息 ID。因此,我们应用相关标识符;一旦回复者确定该消息无效,它将将该消息的主ID复制到其相关ID以保留该消息的原始ID的记录。处理此无效消息的代码位于Replier类(如前面所示)的onMessage方法中。Requestor.receiveSync()包含类似的无效消息处理代码。

                                                                                                                                            One in­sight worth noting is that when the mes­sage is moved to the in­valid mes­sage queue, it is ac­tu­ally being resent, so it gets a new mes­sage ID. Be­cause of this, we apply Cor­rel­a­tion Iden­ti­fier; once the replier de­term­ines the mes­sage to be in­valid, it copies the mes­sage's main ID to its cor­rel­a­tion ID to pre­serve a record of the mes­sage's ori­ginal ID. The code that handles this in­valid-mes­sage pro­cess­ing is in the Replier class, shown earlier, in the on­Mes­sage method. Re­questor.re­ceive­Sync() con­tains sim­ilar in­valid-mes­sage pro­cess­ing code.

                                                                                                                                            结论

                                                                                                                                            Con­clu­sions

                                                                                                                                            我们已经了解了如何实现两个类:RequestorReplier(消息端点),它们使用Request - Reply 交换请求和回复消息。请求消息使用返回地址来指定将回复发送到哪个队列。回复消息使用相关标识符来指定这是对哪个请求的回复。请求者实现轮询消费者来接收回复,而回复者实现事件驱动消费者来接收请求。请求和回复队列是数据类型通道; 当消费者收到类型不正确的消息时,它会将消息重新路由到无效消息通道

                                                                                                                                            We've seen how to im­ple­ment two classes, Re­questor and Replier (Mes­sage En­d­points), that ex­change re­quest and reply Mes­sages using Re­quest-Reply. The re­quest mes­sage uses a Return Ad­dress to spe­cify what queue to send the reply on. The reply mes­sage uses a Cor­rel­a­tion Iden­ti­fier to spe­cify which re­quest this is a reply for. The re­questor im­ple­ments a Polling Con­sumer to re­ceive replies, whereas the replier im­ple­ments an Event-Driven Con­sumer to re­ceive re­quests. The re­quest and reply queues are Data­type Chan­nels; when a con­sumer re­ceives a mes­sage that is not of the right type, it reroutes the mes­sage to the In­valid Mes­sage Chan­nel.

                                                                                                                                              .NET 请求-回复示例

                                                                                                                                              .NET Request-Reply Example

                                                                                                                                              这是如何使用消息传递的简单示例,以 .NET [ SysMsg ] 和 C# 实现。它展示了如何实现Request-Reply,其中请求者应用程序发送请求,回复者应用程序接收请求并返回回复,然后请求者接收回复。它还显示了如何将无效消息重新路由到特殊通道。

                                                                                                                                              This is a simple ex­ample of how to use mes­saging, im­ple­men­ted in .NET [SysMsg] and C#. It shows how to im­ple­ment Re­quest-Reply, where a re­questor ap­plic­a­tion sends a re­quest, a replier ap­plic­a­tion re­ceives the re­quest and re­turns a reply, and the re­questor re­ceives the reply. It also shows how an in­valid mes­sage will be rerouted to a spe­cial chan­nel.

                                                                                                                                              请求-答复示例的组成部分

                                                                                                                                              Com­pon­ents of the Re­quest-Reply Ex­ample

                                                                                                                                              图形/06inf02.gif

                                                                                                                                              此示例是使用 Microsoft .NET Framework SDK 开发的,并在安装了 MSMQ [ MSMQ ]的 Windows XP 计算机上运行。

                                                                                                                                              This ex­ample was de­ve­loped using the Mi­crosoft .NET Frame­work SDK and run on a Win­dows XP com­puter with MSMQ [MSMQ] in­stalled.

                                                                                                                                              请求-回复示例

                                                                                                                                              Re­quest-Reply Ex­ample

                                                                                                                                              该示例由两个主要类组成。

                                                                                                                                              This ex­ample con­sists of two main classes.

                                                                                                                                              1. 请求者发送请求消息并等待接收回复消息作为响应的 消息。

                                                                                                                                              2. Re­questor A Mes­sage En­d­point that sends a re­quest mes­sage and waits to re­ceive a reply mes­sage as a re­sponse.

                                                                                                                                              3. Replier等待接收请求消息的消息端点 ;当它出现时,它会通过发送回复消息进行响应。

                                                                                                                                              4. Replier A Mes­sage En­d­point that waits to re­ceive the re­quest mes­sage; when it does, it re­sponds by send­ing the reply mes­sage.

                                                                                                                                              请求者和回复者将各自作为单独的 .NET 程序运行,这就是分布式通信的原因。

                                                                                                                                              The re­questor and the replier will each run as a sep­ar­ate .NET pro­gram, which is what makes the com­mu­nic­a­tion dis­trib­uted.

                                                                                                                                              此示例假设消息传递系统定义了以下三个队列:

                                                                                                                                              This ex­ample as­sumes that the mes­saging system has these three queues defined:

                                                                                                                                              1. .\private$\ RequestQueue请求者用于将请求消息发送到回复者的MessageQueue 。

                                                                                                                                              2. .\private$\Re­questQueue The Mes­sageQueue the re­questor uses to send the re­quest mes­sage to the replier.

                                                                                                                                              3. .\private$\ReplyQueue 回复者用来向请求者发送回复消息的MessageQueue

                                                                                                                                              4. .\private$\ReplyQueue The Mes­sageQueue the replier uses to send the reply mes­sage to the re­questor.

                                                                                                                                              5. .\private$\InvalidQueue请求者和回复者收到无法解释的消息时将消息移至的 MessageQueue

                                                                                                                                              6. .\private$\In­val­idQueue The Mes­sageQueue to which the re­questor and the replier move a mes­sage when they re­ceive a mes­sage they cannot in­ter­pret.

                                                                                                                                              该示例的工作原理如下。当请求程序在命令行窗口中启动时,它会启动并打印如下输出:

                                                                                                                                              Here's how the ex­ample works. When the re­questor is star­ted in a com­mand-line window, it starts and prints output like this:

                                                                                                                                              发送请求
                                                                                                                                                      时间:09:11:09.165342
                                                                                                                                                      消息 ID:8b0fc389-f21f-423b-9eaa-c3a881a34808\149
                                                                                                                                                      科雷尔。ID:
                                                                                                                                                      回复:.\private$\ReplyQueue
                                                                                                                                                      内容:世界你好。
                                                                                                                                              
                                                                                                                                              Sent re­quest
                                                                                                                                                      Time:       09:11:09.165342
                                                                                                                                                      Mes­sage ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\149
                                                                                                                                                      Correl. ID:
                                                                                                                                                      Reply to:   .\private$\ReplyQueue
                                                                                                                                                      Con­tents:   Hello world.
                                                                                                                                              

                                                                                                                                              这表明请求者已经发送了请求消息。请注意,即使应答器甚至没有运行并因此无法接收请求,这仍然有效。

                                                                                                                                              This shows that the re­questor has sent a re­quest mes­sage. Notice that this works even though the replier isn't even run­ning and there­fore cannot re­ceive the re­quest.

                                                                                                                                              当回复器在另一个命令行窗口中启动时,它会启动并打印如下输出:

                                                                                                                                              When the replier is star­ted in an­other com­mand-line window, it starts and prints output like this:

                                                                                                                                              收到请求
                                                                                                                                                      时间:09:11:09.375644
                                                                                                                                                      消息 ID:8b0fc389-f21f-423b-9eaa-c3a881a34808\149
                                                                                                                                                      科雷尔。ID:<不适用>
                                                                                                                                                      回复:FORMATNAME:DIRECT=OS:XYZ123\private$\ReplyQueue
                                                                                                                                                      内容:世界你好。
                                                                                                                                              已发送回复
                                                                                                                                                      时间:09:11:09.956480
                                                                                                                                                      消息 ID:8b0fc389-f21f-423b-9eaa-c3a881a34808\150
                                                                                                                                                      科雷尔。编号:8b0fc389-f21f-423b-9eaa-c3a881a34808\149
                                                                                                                                                      回复:<n/a>
                                                                                                                                                      内容:世界你好。
                                                                                                                                              
                                                                                                                                              Re­ceived re­quest
                                                                                                                                                      Time:       09:11:09.375644
                                                                                                                                                      Mes­sage ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\149
                                                                                                                                                      Correl. ID: <n/a>
                                                                                                                                                      Reply to:   FORM­AT­NAME:DIRECT=OS:XYZ123\private$\ReplyQueue
                                                                                                                                                      Con­tents:   Hello world.
                                                                                                                                              Sent reply
                                                                                                                                                      Time:       09:11:09.956480
                                                                                                                                                      Mes­sage ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\150
                                                                                                                                                      Correl. ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\149
                                                                                                                                                      Reply to:   <n/a>
                                                                                                                                                      Con­tents:   Hello world.
                                                                                                                                              

                                                                                                                                              这表明回复者收到了请求消息并发送了回复消息。

                                                                                                                                              This shows that the replier re­ceived the re­quest mes­sage and sent a reply mes­sage.

                                                                                                                                              此输出中有几个有趣的项目。首先,注意请求发送接收的时间戳;请求在发送后被接收(210302 毫秒后)。其次,请注意,这两种情况下的消息 ID 相同,因为它是同一条消息。第三,请注意,Hello world 的内容是相同的,这是有道理的,因为这是发送的数据,并且双方必须相同。第四,请求消息指定作为回复消息的目的地的队列(返回地址的示例)

                                                                                                                                              There are sev­eral in­ter­est­ing items in this output. First, notice the re­quest sent and re­ceived timestamps; the re­quest was re­ceived after it was sent (210302 ms later). Second, notice that the mes­sage ID is the same in both cases, be­cause it's the same mes­sage. Third, notice that the con­tents, Hello world, are the same, which makes sense be­cause that's the data that was sent, and it must be the same on both sides. Fourth, the re­quest mes­sage spe­cifies the queue that is the des­tin­a­tion for the reply mes­sage (an ex­ample of Return Ad­dress).

                                                                                                                                              接下来,我们将接收请求的输出与发送回复的输出进行比较。首先,请注意,直到收到请求后(580836 毫秒后)才发送回复。其次,回复的消息ID与请求的消息ID不同;这是因为请求消息和回复消息是不同的、单独的消息。第三,请求的内容已被提取并添加到回复中。第四,回复目的地未指定,因为不需要回复(回复不使用Return Address )。第五,回复的相关 ID 与请求的消息 ID 相同(回复确实使用Correlation Identifier )

                                                                                                                                              Next, let's com­pare the output from re­ceiv­ing the re­quest to that for send­ing the reply. First, notice the reply was not sent until after the re­quest was re­ceived (580836 ms after). Second, the mes­sage ID for the reply is dif­fer­ent from that for the re­quest; this is be­cause the re­quest and reply mes­sages are dif­fer­ent, sep­ar­ate mes­sages. Third, the con­tents of the re­quest have been ex­trac­ted and added to the reply. Fourth, the reply-to des­tin­a­tion is un­spe­cified be­cause no reply is ex­pec­ted (the reply does not use Return Ad­dress). Fifth, the reply's cor­rel­a­tion ID is the same as the re­quest's mes­sage ID (the reply does use Cor­rel­a­tion Iden­ti­fier).

                                                                                                                                              最后,回到第一个窗口,请求者收到以下回复:

                                                                                                                                              Fi­nally, back in the first window, the re­questor re­ceived the fol­low­ing reply:

                                                                                                                                              收到回复
                                                                                                                                                      时间:09:11:10.156467
                                                                                                                                                      消息 ID:8b0fc389-f21f-423b-9eaa-c3a881a34808\150
                                                                                                                                                      科雷尔。编号:8b0fc389-f21f-423b-9eaa-c3a881a34808\149
                                                                                                                                                      回复:<n/a>
                                                                                                                                                      内容:世界你好。
                                                                                                                                              
                                                                                                                                              Re­ceived reply
                                                                                                                                                      Time:       09:11:10.156467
                                                                                                                                                      Mes­sage ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\150
                                                                                                                                                      Correl. ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\149
                                                                                                                                                      Reply to:   <n/a>
                                                                                                                                                      Con­tents:   Hello world.
                                                                                                                                              

                                                                                                                                              此输出包含几个感兴趣的项目。发送后大约两秒就收到了回复。回复的消息ID在接收时和发送时是相同的,这证明确实是同一条消息。接收到的消息内容与发送的消息内容相同,相关ID告诉请求者这个回复是针对哪个请求的( Correlation Identifier)

                                                                                                                                              This output con­tains sev­eral items of in­terest. The reply was re­ceived about two seconds after it was sent. The mes­sage ID of the reply was the same when it was re­ceived as when it was sent, which proves that it is indeed the same mes­sage. The mes­sage con­tents re­ceived are the same as those sent, and the cor­rel­a­tion ID tells the re­questor which re­quest this reply is for (Cor­rel­a­tion Iden­ti­fier).

                                                                                                                                              请求者不会运行很长时间;它发送请求,接收回复,然后退出。但是,回复程序会连续运行,等待请求并发送回复。要停止应答器,我们进入其命令 shell 窗口并按回车键,这会导致应答器程序退出。

                                                                                                                                              The re­questor doesn't run for very long; it sends a re­quest, re­ceives a reply, and exits. How­ever, the replier runs con­tinu­ously, wait­ing for re­quests and send­ing replies. To stop the replier, we go to its com­mand shell window and press the return key, which causes the replier pro­gram to exit.

                                                                                                                                              这就是 .NET 请求-答复示例。请求者已准备并发送请求。回复者收到请求并发送回复。然后,请求者收到了对其原始请求的答复。

                                                                                                                                              So, that's the .NET re­quest-reply ex­ample. A re­quest was pre­pared and sent by the re­questor. The replier re­ceived the re­quest and sent a reply. Then, the re­questor re­ceived the reply to its ori­ginal re­quest.

                                                                                                                                              请求-回复代码

                                                                                                                                              Re­quest-Reply Code

                                                                                                                                              首先我们看一下请求者是如何实现的。

                                                                                                                                              First, let's take a look at how the re­questor is im­ple­men­ted.

                                                                                                                                              使用系统;
                                                                                                                                              使用系统消息传递;
                                                                                                                                              
                                                                                                                                              公开课请求者
                                                                                                                                              {
                                                                                                                                                  私有消息队列请求队列;
                                                                                                                                                  私有消息队列回复队列;
                                                                                                                                              
                                                                                                                                                  公共请求者(字符串请求队列名称,字符串回复队列名称)
                                                                                                                                                  {
                                                                                                                                                      requestQueue = new MessageQueue(requestQueueName);
                                                                                                                                                      回复队列 = new MessageQueue(回复队列名称);
                                                                                                                                              
                                                                                                                                                      replyQueue.MessageReadPropertyFilter.SetAll();
                                                                                                                                                      ((XmlMessageFormatter)replyQueue.Formatter).TargetTypeNames =
                                                                                                                                                        新字符串[] {“System.String,mscorlib”};
                                                                                                                                                  }
                                                                                                                                              
                                                                                                                                              
                                                                                                                                                  公共无效发送()
                                                                                                                                                  {
                                                                                                                                                      消息 requestMessage = new Message();
                                                                                                                                                      requestMessage.Body = "你好世界。";
                                                                                                                                                      requestMessage.ResponseQueue = 回复队列;
                                                                                                                                                      requestQueue.Send(requestMessage);
                                                                                                                                              
                                                                                                                                              
                                                                                                                                                      Console.WriteLine("已发送请求");
                                                                                                                                                      Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
                                                                                                                                                      Console.WriteLine("\t消息 ID: {0}", requestMessage.Id);
                                                                                                                                                      Console.WriteLine("\tCorrel.ID: {0}", requestMessage.CorrelationId);
                                                                                                                                                      Console.WriteLine("\t回复:{0}", requestMessage.ResponseQueue.Path);
                                                                                                                                                      Console.WriteLine("\tContents: {0}", requestMessage.Body.ToString());
                                                                                                                                                  }
                                                                                                                                              
                                                                                                                                              
                                                                                                                                                  公共无效接收同步()
                                                                                                                                                  {
                                                                                                                                                      消息回复消息=replyQueue.Receive();
                                                                                                                                              
                                                                                                                                              
                                                                                                                                                      Console.WriteLine("收到回复");
                                                                                                                                                      Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
                                                                                                                                                      Console.WriteLine("\t消息 ID: {0}",replyMessage.Id);
                                                                                                                                                      Console.WriteLine("\tCorrel.ID: {0}",replyMessage.CorrelationId);
                                                                                                                                                      Console.WriteLine("\t回复: {0}", "<n/a>");
                                                                                                                                                      Console.WriteLine("\tContents: {0}",replyMessage.Body.ToString());
                                                                                                                                                  }
                                                                                                                                              }
                                                                                                                                              
                                                                                                                                              using System;
                                                                                                                                              using System.Mes­saging;
                                                                                                                                              
                                                                                                                                              public class Re­questor
                                                                                                                                              {
                                                                                                                                                  private Mes­sageQueue re­questQueue;
                                                                                                                                                  private Mes­sageQueue replyQueue;
                                                                                                                                              
                                                                                                                                                  public Re­questor(String re­questQueueName, String replyQueueName)
                                                                                                                                                  {
                                                                                                                                                      re­questQueue = new Mes­sageQueue(re­questQueueName);
                                                                                                                                                      replyQueue = new Mes­sageQueue(replyQueueName);
                                                                                                                                              
                                                                                                                                                      replyQueue.Mes­sageRead­Prop­er­ty­Fil­ter.SetAll();
                                                                                                                                                      ((Xm­lMes­sage­Format­ter)replyQueue.Format­ter).Tar­get­Type­Names =
                                                                                                                                                        new string[]{"System.String,mscorlib"};
                                                                                                                                                  }
                                                                                                                                              
                                                                                                                                              
                                                                                                                                                  public void Send()
                                                                                                                                                  {
                                                                                                                                                      Mes­sage re­quest­Mes­sage = new Mes­sage();
                                                                                                                                                      re­quest­Mes­sage.Body = "Hello world.";
                                                                                                                                                      re­quest­Mes­sage.Re­spon­se­Queue = replyQueue;
                                                                                                                                                      re­questQueue.Send(re­quest­Mes­sage);
                                                                                                                                              
                                                                                                                                              
                                                                                                                                                      Con­sole.WriteLine("Sent re­quest");
                                                                                                                                                      Con­sole.WriteLine("\tTime:       {0}", Dat­e­Time.Now.To­String("HH:mm:ss.ffffff"));
                                                                                                                                                      Con­sole.WriteLine("\tMes­sage ID: {0}", re­quest­Mes­sage.Id);
                                                                                                                                                      Con­sole.WriteLine("\tCor­rel. ID: {0}", re­quest­Mes­sage.Cor­rel­a­tionId);
                                                                                                                                                      Con­sole.WriteLine("\tReply to:   {0}", re­quest­Mes­sage.Re­spon­se­Queue.Path);
                                                                                                                                                      Con­sole.WriteLine("\tCon­tents:   {0}", re­quest­Mes­sage.Body.To­String());
                                                                                                                                                  }
                                                                                                                                              
                                                                                                                                              
                                                                                                                                                  public void Re­ceive­Sync()
                                                                                                                                                  {
                                                                                                                                                      Mes­sage replyMes­sage = replyQueue.Re­ceive();
                                                                                                                                              
                                                                                                                                              
                                                                                                                                                      Con­sole.WriteLine("Re­ceived reply");
                                                                                                                                                      Con­sole.WriteLine("\tTime:       {0}", Dat­e­Time.Now.To­String("HH:mm:ss.ffffff"));
                                                                                                                                                      Con­sole.WriteLine("\tMes­sage ID: {0}", replyMes­sage.Id);
                                                                                                                                                      Con­sole.WriteLine("\tCor­rel. ID: {0}", replyMes­sage.Cor­rel­a­tionId);
                                                                                                                                                      Con­sole.WriteLine("\tReply to:   {0}", "<n/a>");
                                                                                                                                                      Con­sole.WriteLine("\tCon­tents:   {0}", replyMes­sage.Body.To­String());
                                                                                                                                                  }
                                                                                                                                              }
                                                                                                                                              

                                                                                                                                              想要发送请求和接收回复的应用程序可以使用请求者来执行此操作。应用程序指定两个队列的路径名:请求队列和应答队列。这是请求者初始化自身所需的信息。

                                                                                                                                              An ap­plic­a­tion that wants to send re­quests and re­ceive replies could use a re­questor to do so. The ap­plic­a­tion spe­cifies the path­names of two queues: the re­quest queue and the reply queue. This is the in­form­a­tion the re­questor needs to ini­tial­ize itself.

                                                                                                                                              在请求者构造函数中,请求者使用队列名称连接到消息传递系统。

                                                                                                                                              In the re­questor con­structor, the re­questor uses the queue names to con­nect to the mes­saging system.

                                                                                                                                              • 它使用队列名称来查找队列,即MessageQueue。这些名称是 MSMQ 资源的路径名。

                                                                                                                                              • It uses the queue names to look up the queues, which are Mes­sageQueues. The names are path­names to MSMQ re­sources.

                                                                                                                                              • 它设置回复队列的属性过滤器,以便当从队列中读取消息时,该消息的所有属性也将被读取。它还设置格式化程序的TargetTypeNames,以便消息内容将被解释为字符串。

                                                                                                                                              • It sets the reply queue's prop­erty filter so that when a mes­sage is read from the queue, all of the mes­sage's prop­er­ties will be read as well. It also sets the format­ter's Tar­get­Type­Names so that the mes­sage con­tents will be in­ter­preted as strings.

                                                                                                                                              请求者必须能够做的一件事是发送请求消息。为此,它实现了Send()方法。

                                                                                                                                              One thing that the re­questor must be able to do is send re­quest mes­sages. For that, it im­ple­ments the Send() method.

                                                                                                                                              • 它创建一条消息并将其内容设置为“Hello world”。

                                                                                                                                              • It cre­ates a mes­sage and sets its con­tents to "Hello world."

                                                                                                                                              • 它将消息的ResponseQueue属性设置为回复队列。这是一个返回地址,它将告诉回复者如何发回回复。

                                                                                                                                              • It sets the mes­sage's Re­spon­se­Queue prop­erty to be the reply queue. This is a Return Ad­dress that will tell the replier how to send back the reply.

                                                                                                                                              • 然后它将消息发送到队列。

                                                                                                                                              • It then sends the mes­sage to the queue.

                                                                                                                                              • 然后它打印出刚刚发送的消息的详细信息。这是在消息发送后完成的,因为消息 ID 由消息传递系统设置,直到消息实际发送后才设置。

                                                                                                                                              • It then prints out the de­tails of the mes­sage it just sent. This is done after the mes­sage is sent be­cause the mes­sage ID is set by the mes­saging system and is not set until the mes­sage is ac­tu­ally sent.

                                                                                                                                              请求者必须能够做的另一件事是接收回复消息。为此,它实现了ReceiveSync()方法。

                                                                                                                                              The other thing the re­questor must be able to do is re­ceive reply mes­sages. It im­ple­ments the Re­ceive­Sync() method for this pur­pose.

                                                                                                                                              • 它运行队列的Receive()方法来获取消息,该方法会同步阻塞,直到消息被传递到队列并从队列中读取,因此请求者是一个Polling Consumer 。由于此接收是同步的,因此请求者的方法称为ReceiveSync()

                                                                                                                                              • It runs the queue's Re­ceive() method to get the mes­sage, which syn­chron­ously blocks until a mes­sage is de­livered to the queue and is read from the queue, so the re­questor is a Polling Con­sumer. Be­cause this re­ceive is syn­chron­ous, the re­questor's method is called Re­ceive­Sync().

                                                                                                                                              • 请求者获取消息的内容并打印出消息的详细信息。

                                                                                                                                              • The re­questor gets the mes­sage's con­tents and prints out the mes­sage's de­tails.

                                                                                                                                              通过这种方式,请求者可以执行发送请求和接收回复所需的一切操作。

                                                                                                                                              In this way, a re­questor does everything ne­ces­sary to send a re­quest and re­ceive a reply.

                                                                                                                                              接下来我们看看replier是如何实现的。

                                                                                                                                              Next, let's take a look at how the replier is im­ple­men­ted.

                                                                                                                                              使用系统;
                                                                                                                                              使用系统消息传递;
                                                                                                                                              
                                                                                                                                              回复者类{
                                                                                                                                              
                                                                                                                                                  私有消息队列无效队列;
                                                                                                                                              
                                                                                                                                                  公共回复者(字符串请求队列名称,字符串无效队列名称)
                                                                                                                                                  {
                                                                                                                                                      MessageQueue requestQueue = new MessageQueue(requestQueueName);
                                                                                                                                                      invalidQueue = new MessageQueue(invalidQueueName);
                                                                                                                                              
                                                                                                                                                      requestQueue.MessageReadPropertyFilter.SetAll();
                                                                                                                                                      ((XmlMessageFormatter)requestQueue.Formatter).TargetTypeNames =
                                                                                                                                                        新字符串[] {“System.String,mscorlib”};
                                                                                                                                              
                                                                                                                                                      requestQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnReceiveCompleted);
                                                                                                                                                      requestQueue.BeginReceive();
                                                                                                                                                  }
                                                                                                                                              
                                                                                                                                              
                                                                                                                                                  公共无效OnReceiveCompleted(对象源,ReceiveCompletedEventArgs asyncResult)
                                                                                                                                                  {
                                                                                                                                                      消息队列请求队列 = (消息队列)源;
                                                                                                                                                      消息 requestMessage = requestQueue.EndReceive(asyncResult.AsyncResult);
                                                                                                                                              
                                                                                                                                                      尝试
                                                                                                                                                      {
                                                                                                                                                          Console.WriteLine("收到请求");
                                                                                                                                                          Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
                                                                                                                                                          Console.WriteLine("\t消息 ID: {0}", requestMessage.Id);
                                                                                                                                                          Console.WriteLine("\tCorrel.ID: {0}", "<n/a>");
                                                                                                                                                          Console.WriteLine("\t回复:{0}", requestMessage.ResponseQueue.Path);
                                                                                                                                                          Console.WriteLine("\tContents: {0}", requestMessage.Body.ToString());
                                                                                                                                              
                                                                                                                                                          字符串内容 = requestMessage.Body.ToString();
                                                                                                                                                          MessageQueue 回复队列 = requestMessage.ResponseQueue;
                                                                                                                                                          消息回复Message = new Message();
                                                                                                                                                          回复消息.Body = 内容;
                                                                                                                                                          回复消息.CorrelationId = 请求消息.Id;
                                                                                                                                                          回复队列.Send(replyMessage);
                                                                                                                                              
                                                                                                                                                          Console.WriteLine("已发送回复");
                                                                                                                                                          Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
                                                                                                                                                          Console.WriteLine("\t消息 ID: {0}",replyMessage.Id);
                                                                                                                                                          Console.WriteLine("\tCorrel.ID: {0}",replyMessage.CorrelationId);
                                                                                                                                                          Console.WriteLine("\t回复: {0}", "<n/a>");
                                                                                                                                                          Console.WriteLine("\tContents: {0}",replyMessage.Body.ToString());
                                                                                                                                                      }
                                                                                                                                                      捕获(异常){
                                                                                                                                                          Console.WriteLine("检测到无效消息");
                                                                                                                                                          Console.WriteLine("\tType: {0}", requestMessage.BodyType);
                                                                                                                                                          Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
                                                                                                                                                          Console.WriteLine("\t消息 ID: {0}", requestMessage.Id);
                                                                                                                                                          Console.WriteLine("\tCorrel.ID: {0}", "<n/a>");
                                                                                                                                                          Console.WriteLine("\t回复: {0}", "<n/a>");
                                                                                                                                              
                                                                                                                                                          requestMessage.CorrelationId = requestMessage.Id;
                                                                                                                                              
                                                                                                                                                          invalidQueue.Send(requestMessage);
                                                                                                                                              
                                                                                                                                                          Console.WriteLine("发送到无效消息队列");
                                                                                                                                                          Console.WriteLine("\tType: {0}", requestMessage.BodyType);
                                                                                                                                                          Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
                                                                                                                                                          Console.WriteLine("\t消息 ID: {0}", requestMessage.Id);
                                                                                                                                                          Console.WriteLine("\tCorrel.ID: {0}", requestMessage.CorrelationId);
                                                                                                                                                          Console.WriteLine("\t回复:{0}", requestMessage.ResponseQueue.Path);
                                                                                                                                                      }
                                                                                                                                              
                                                                                                                                                      requestQueue.BeginReceive();
                                                                                                                                                  }
                                                                                                                                              }
                                                                                                                                              
                                                                                                                                              using System;
                                                                                                                                              using System.Mes­saging;
                                                                                                                                              
                                                                                                                                              class Replier {
                                                                                                                                              
                                                                                                                                                  private Mes­sageQueue in­val­idQueue;
                                                                                                                                              
                                                                                                                                                  public Replier(String re­questQueueName, String in­val­idQueueName)
                                                                                                                                                  {
                                                                                                                                                      Mes­sageQueue re­questQueue = new Mes­sageQueue(re­questQueueName);
                                                                                                                                                      in­val­idQueue = new Mes­sageQueue(in­val­idQueueName);
                                                                                                                                              
                                                                                                                                                      re­questQueue.Mes­sageRead­Prop­er­ty­Fil­ter.SetAll();
                                                                                                                                                      ((Xm­lMes­sage­Format­ter)re­questQueue.Format­ter).Tar­get­Type­Names =
                                                                                                                                                        new string[]{"System.String,mscorlib"};
                                                                                                                                              
                                                                                                                                                      re­questQueue.Re­ceive­Com­pleted += new Re­ceive­Com­plete­dE­ventHand­ler(On­Re­ceive­Com­pleted);
                                                                                                                                                      re­questQueue.Be­gin­Re­ceive();
                                                                                                                                                  }
                                                                                                                                              
                                                                                                                                              
                                                                                                                                                  public void On­Re­ceive­Com­pleted(Object source, Re­ceive­Com­plete­dEvent­Args asyn­cRes­ult)
                                                                                                                                                  {
                                                                                                                                                      Mes­sageQueue re­questQueue = (Mes­sageQueue)source;
                                                                                                                                                      Mes­sage re­quest­Mes­sage = re­questQueue.En­dRe­ceive(asyn­cRes­ult.Asyn­cRes­ult);
                                                                                                                                              
                                                                                                                                                      try
                                                                                                                                                      {
                                                                                                                                                          Con­sole.WriteLine("Re­ceived re­quest");
                                                                                                                                                          Con­sole.WriteLine("\tTime:       {0}", Dat­e­Time.Now.To­String("HH:mm:ss.ffffff"));
                                                                                                                                                          Con­sole.WriteLine("\tMes­sage ID: {0}", re­quest­Mes­sage.Id);
                                                                                                                                                          Con­sole.WriteLine("\tCor­rel. ID: {0}", "<n/a>");
                                                                                                                                                          Con­sole.WriteLine("\tReply to:   {0}", re­quest­Mes­sage.Re­spon­se­Queue.Path);
                                                                                                                                                          Con­sole.WriteLine("\tCon­tents:   {0}", re­quest­Mes­sage.Body.To­String());
                                                                                                                                              
                                                                                                                                                          string con­tents = re­quest­Mes­sage.Body.To­String();
                                                                                                                                                          Mes­sageQueue replyQueue = re­quest­Mes­sage.Re­spon­se­Queue;
                                                                                                                                                          Mes­sage replyMes­sage = new Mes­sage();
                                                                                                                                                          replyMes­sage.Body = con­tents;
                                                                                                                                                          replyMes­sage.Cor­rel­a­tionId = re­quest­Mes­sage.Id;
                                                                                                                                                          replyQueue.Send(replyMes­sage);
                                                                                                                                              
                                                                                                                                                          Con­sole.WriteLine("Sent reply");
                                                                                                                                                          Con­sole.WriteLine("\tTime:       {0}", Dat­e­Time.Now.To­String("HH:mm:ss.ffffff"));
                                                                                                                                                          Con­sole.WriteLine("\tMes­sage ID: {0}", replyMes­sage.Id);
                                                                                                                                                          Con­sole.WriteLine("\tCor­rel. ID: {0}", replyMes­sage.Cor­rel­a­tionId);
                                                                                                                                                          Con­sole.WriteLine("\tReply to:   {0}", "<n/a>");
                                                                                                                                                          Con­sole.WriteLine("\tCon­tents:   {0}", replyMes­sage.Body.To­String());
                                                                                                                                                      }
                                                                                                                                                      catch ( Ex­cep­tion ) {
                                                                                                                                                          Con­sole.WriteLine("In­valid mes­sage de­tec­ted");
                                                                                                                                                          Con­sole.WriteLine("\tType:       {0}", re­quest­Mes­sage.Bo­dy­Type);
                                                                                                                                                          Con­sole.WriteLine("\tTime:       {0}", Dat­e­Time.Now.To­String("HH:mm:ss.ffffff"));
                                                                                                                                                          Con­sole.WriteLine("\tMes­sage ID: {0}", re­quest­Mes­sage.Id);
                                                                                                                                                          Con­sole.WriteLine("\tCor­rel. ID: {0}", "<n/a>");
                                                                                                                                                          Con­sole.WriteLine("\tReply to:   {0}", "<n/a>");
                                                                                                                                              
                                                                                                                                                          re­quest­Mes­sage.Cor­rel­a­tionId = re­quest­Mes­sage.Id;
                                                                                                                                              
                                                                                                                                                          in­val­idQueue.Send(re­quest­Mes­sage);
                                                                                                                                              
                                                                                                                                                          Con­sole.WriteLine("Sent to in­valid mes­sage queue");
                                                                                                                                                          Con­sole.WriteLine("\tType:       {0}", re­quest­Mes­sage.Bo­dy­Type);
                                                                                                                                                          Con­sole.WriteLine("\tTime:       {0}", Dat­e­Time.Now.To­String("HH:mm:ss.ffffff"));
                                                                                                                                                          Con­sole.WriteLine("\tMes­sage ID: {0}", re­quest­Mes­sage.Id);
                                                                                                                                                          Con­sole.WriteLine("\tCor­rel. ID: {0}", re­quest­Mes­sage.Cor­rel­a­tionId);
                                                                                                                                                          Con­sole.WriteLine("\tReply to:   {0}", re­quest­Mes­sage.Re­spon­se­Queue.Path);
                                                                                                                                                      }
                                                                                                                                              
                                                                                                                                                      re­questQueue.Be­gin­Re­ceive();
                                                                                                                                                  }
                                                                                                                                              }
                                                                                                                                              

                                                                                                                                              当应用程序需要接收请求并发送回复时,它将实现类似此回复程序的功能。应用程序指定请求和无效消息队列的路径名。(它不需要指定回复队列的名称,因为正如我们稍后将看到的,该名称将由消息的Return Address 提供。 )这是请求者初始化自身所需的信息。

                                                                                                                                              When an ap­plic­a­tion needs to re­ceive a re­quest and send a reply, it will im­ple­ment some­thing like this replier. The ap­plic­a­tion spe­cifies the path­names of the re­quest and in­valid mes­sage queues. (It does not need to spe­cify the name of the reply queue be­cause, as we'll see later, that will be provided by the mes­sage's Return Ad­dress.) This is the in­form­a­tion the re­questor needs to ini­tial­ize itself.

                                                                                                                                              回复者构造函数与请求者的构造函数非常相似,但有一些差异。

                                                                                                                                              The replier con­structor is pretty sim­ilar to the re­questor's, but there are a couple of dif­fer­ences.

                                                                                                                                              • 一个区别是回复者不查找回复队列。这是因为回复者并不认为它总是会在该队列上发送回复;相反,正如我们将看到的,它会让请求消息告诉它在哪个队列上发送回复消息。

                                                                                                                                              • One dif­fer­ence is that the replier does not look up the reply queue. This is be­cause the replier does not assume it will always send replies on that queue; rather, as we'll see, it will let the re­quest mes­sage tell it what queue to send the reply mes­sage on.

                                                                                                                                              • 另一个区别是回复者是事件驱动的 Consumer ,因此它设置了ReceiveCompletedEventHandler。当消息传递到请求队列时,消息系统将自动调用指定的方法OnReceiveCompleted

                                                                                                                                              • An­other dif­fer­ence is that the replier is an Event-Driven Con­sumer, so it sets up a Re­ceive­Com­plete­dE­ventHand­ler. When a mes­sage is de­livered to the re­quest queue, the mes­saging system will auto­mat­ic­ally call the spe­cified method, On­Re­ceive­Com­pleted.

                                                                                                                                              应答器将自身初始化为请求队列上的侦听器,然后简单地等待消息到达。与必须显式检查回复队列中的消息的请求者不同,回复者是事件驱动的,因此在消息传递系统使用新消息调用其OnReceiveCompleted方法之前不执行任何操作。该消息将来自请求队列,因为构造函数在请求队列上创建了事件处理程序。调用OnReceiveCompleted后,它会执行以下操作来获取新消息并对其进行处理:

                                                                                                                                              The replier ini­tial­izes itself as a listener on the re­quest queue, and then simply waits for mes­sages to arrive. Unlike the re­questor, which has to ex­pli­citly check the reply queue for mes­sages, the replier is event-driven and so does noth­ing until the mes­saging system calls its On­Re­ceive­Com­pleted method with a new mes­sage. The mes­sage will be from the re­quest queue be­cause the con­structor cre­ated the event hand­ler on the re­quest queue. Once On­Re­ceive­Com­pleted is called, this is what it does to get the new mes­sage and pro­cesses it:

                                                                                                                                              • 源是MessageQueue ,即请求队列。

                                                                                                                                              • The source is a Mes­sageQueue, the re­quest queue.

                                                                                                                                              • 消息本身是通过运行队列的EndReceive方法获取的。然后回复者打印出有关该消息的详细信息。

                                                                                                                                              • The mes­sage itself is ob­tained by run­ning the queue's En­dRe­ceive method. The replier then prints out the de­tails about the mes­sage.

                                                                                                                                              • 应答器实现其返回地址部分。请记住,请求者设置请求消息的响应队列属性来指定回复队列。回复者现在获取该属性的值并使用它来引用正确的MessageQueue 。这里重要的部分是回复器没有被硬编码为使用特定的回复队列;它使用每个特定请求消息指定的任何回复队列。

                                                                                                                                              • The replier im­ple­ments its part of Return Ad­dress. Re­mem­ber that the re­questor set the re­quest mes­sage's re­sponse-queue prop­erty to spe­cify the reply queue. The replier now gets that prop­erty's value and uses it to ref­er­ence the proper Mes­sageQueue. The im­port­ant part here is that the replier is not hard-coded to use a par­tic­u­lar reply queue; it uses whatever reply queue each par­tic­u­lar re­quest mes­sage spe­cifies.

                                                                                                                                              • 然后回复者创建回复消息。在此过程中,它通过将回复消息的correlation-id属性设置为与请求消息的message-id属性相同的值来实现相关标识符。

                                                                                                                                              • The replier then cre­ates the reply mes­sage. In doing so, it im­ple­ments Cor­rel­a­tion Iden­ti­fier by set­ting the reply mes­sage's cor­rel­a­tion-id prop­erty to the same value as the re­quest mes­sage's mes­sage-id prop­erty.

                                                                                                                                              • 然后回复者发出回复消息并显示其详细信息。

                                                                                                                                              • The replier then sends out the reply mes­sage and dis­plays its de­tails.

                                                                                                                                              • 如果消息可以接收但未成功处理并抛出异常,则回复者将消息重新发送到无效消息队列。在此过程中,它将新消息的相关 ID 设置为原始消息的消息 ID。

                                                                                                                                              • If the mes­sage can be re­ceived but not suc­cess­fully pro­cessed and an ex­cep­tion is thrown, the replier re­sends the mes­sage to the in­valid mes­sage queue. In the pro­cess, it sets the new mes­sage's cor­rel­a­tion ID to the ori­ginal mes­sage's mes­sage ID.

                                                                                                                                              • 一旦回复者完成消息处理,它就会运行BeginReceive 以开始侦听下一条消息。

                                                                                                                                              • Once the replier has fin­ished pro­cess­ing the mes­sage, it runs Be­gin­Re­ceive to start listen­ing for the next mes­sage.

                                                                                                                                              因此,回复者会执行接收消息(可能是请求)并发送回复所需的一切操作。如果它无法回复消息,则会将消息路由到无效消息队列。

                                                                                                                                              Thus, a replier does everything ne­ces­sary to re­ceive a mes­sage (pre­sum­ably a re­quest) and send a reply. If it cannot reply to a mes­sage, it routes the mes­sage to the in­valid mes­sage queue.

                                                                                                                                              无效消息示例

                                                                                                                                              In­valid Mes­sage Ex­ample

                                                                                                                                              现在让我们看一个无效消息通道的示例。请记住,我们需要的队列之一是名为private$\InvalidMessages 的队列。其存在是为了如果 MSMQ 客户端(消息端点)收到它无法处理的消息,它可以将奇怪的消息移动到特殊通道。

                                                                                                                                              Let's now look at an ex­ample of In­valid Mes­sage Chan­nel. Re­mem­ber, one of the queues we need is the one named private$\In­val­idMes­sages. This exists so that if an MSMQ client (a Mes­sage En­d­point) re­ceives a mes­sage it cannot pro­cess, it can move the strange mes­sage to a spe­cial chan­nel.

                                                                                                                                              为了演示无效消息处理,我们设计了一个InvalidMessenger 类。该对象专门用于在请求通道上发送格式不正确的消息。与任何通道一样,请求通道是数据类型通道,因为请求接收者期望请求采用某种格式。无效的信使只是以不同的格式发送消息;当回复者收到消息时,它无法识别消息的格式,因此会将消息移至无效消息队列。

                                                                                                                                              To demon­strate in­valid mes­sage hand­ling, we have de­signed an In­val­idMes­sen­ger class. This object is spe­cific­ally de­signed to send a mes­sage on the re­quest chan­nel whose format is in­cor­rect. Like any chan­nel, the re­quest chan­nel is a Data­type Chan­nel in that the re­quest re­ceiv­ers expect the re­quests to be in a cer­tain format. The in­valid mes­sen­ger simply sends a mes­sage in a dif­fer­ent format; when the replier re­ceives the mes­sage, it does not re­cog­nize the mes­sage's format, so it moves the mes­sage to the in­valid mes­sage queue.

                                                                                                                                              我们将在一个窗口中运行回复程序,并在另一个窗口中运行无效的信使。当无效的信使发送消息时,它会显示如下输出:

                                                                                                                                              We'll run the replier in one window and the in­valid mes­sen­ger in an­other window. When the in­valid mes­sen­ger sends its mes­sage, it dis­plays output like this:

                                                                                                                                              发送请求
                                                                                                                                                      型号:768
                                                                                                                                                      时间:09:39:44.223729
                                                                                                                                                      消息 ID:8b0fc389-f21f-423b-9eaa-c3a881a34808\168
                                                                                                                                                      科雷尔。ID: 00000000-0000-0000-0000-000000000000\0
                                                                                                                                                      回复:.\private$\ReplyQueue
                                                                                                                                              
                                                                                                                                              Sent re­quest
                                                                                                                                                      Type:       768
                                                                                                                                                      Time:       09:39:44.223729
                                                                                                                                                      Mes­sage ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\168
                                                                                                                                                      Correl. ID: 00000000-0000-0000-0000-000000000000\0
                                                                                                                                                      Reply to:   .\private$\ReplyQueue
                                                                                                                                              

                                                                                                                                              类型 768 意味着消息内容的格式是二进制的(而回复者期望内容是文本/XML)。应答者收到无效消息后,将其重新发送到无效消息队列中。

                                                                                                                                              Type 768 means that the format of the mes­sage con­tents is binary (whereas the replier is ex­pect­ing the con­tents to be text/XML). The replier re­ceives the in­valid mes­sage and re­sends it to the in­valid mes­sage queue.

                                                                                                                                              检测到无效消息
                                                                                                                                                      型号:768
                                                                                                                                                      时间:09:39:44.233744
                                                                                                                                                      消息 ID:8b0fc389-f21f-423b-9eaa-c3a881a34808\168
                                                                                                                                                      科雷尔。ID:<不适用>
                                                                                                                                                      回复:<n/a>
                                                                                                                                              发送到无效消息队列
                                                                                                                                                      型号:768
                                                                                                                                                      时间:09:39:44.233744
                                                                                                                                                      消息 ID:8b0fc389-f21f-423b-9eaa-c3a881a34808\169
                                                                                                                                                      科雷尔。编号:8b0fc389-f21f-423b-9eaa-c3a881a34808\168
                                                                                                                                                      回复:FORMATNAME:DIRECT=OS:XYZ123\private$\ReplyQueue
                                                                                                                                              
                                                                                                                                              In­valid mes­sage de­tec­ted
                                                                                                                                                      Type:       768
                                                                                                                                                      Time:       09:39:44.233744
                                                                                                                                                      Mes­sage ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\168
                                                                                                                                                      Correl. ID: <n/a>
                                                                                                                                                      Reply to:   <n/a>
                                                                                                                                              Sent to in­valid mes­sage queue
                                                                                                                                                      Type:       768
                                                                                                                                                      Time:       09:39:44.233744
                                                                                                                                                      Mes­sage ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\169
                                                                                                                                                      Correl. ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\168
                                                                                                                                                      Reply to:   FORM­AT­NAME:DIRECT=OS:XYZ123\private$\ReplyQueue
                                                                                                                                              

                                                                                                                                              一个值得注意的见解是,当消息被移动到无效消息队列时,它实际上是在重新发送,因此它会获得一个新的消息 ID。因此,我们应用相关标识符;一旦回复者确定该消息无效,它将将该消息的主ID复制到其相关ID中,以保留该消息的原始ID的记录。处理此无效消息的代码位于Replier类(如前面所示)的OnReceiveCompleted 方法中

                                                                                                                                              One in­sight worth noting is that when the mes­sage is moved to the in­valid mes­sage queue, it is ac­tu­ally being resent, so it gets a new mes­sage ID. Be­cause of this, we apply Cor­rel­a­tion Iden­ti­fier; once the replier de­term­ines the mes­sage to be in­valid, it copies the mes­sage's main ID to its cor­rel­a­tion ID so as to pre­serve a record of the mes­sage's ori­ginal ID. The code that handles this in­valid-mes­sage pro­cess­ing is in the Replier class, shown earlier, in the On­Re­ceive­Com­pleted method.

                                                                                                                                              结论

                                                                                                                                              Con­clu­sions

                                                                                                                                              我们已经了解了如何实现两个类:RequestorReplier(消息端点),它们使用Request - Reply 交换请求并回复消息。请求消息使用返回地址来指定将回复发送到哪个队列。回复消息使用相关标识符来指定这是对哪个请求的回复。请求者实现轮询消费者来接收回复,而回复者实现事件驱动消费者来接收请求。请求和回复队列是数据类型通道; 当消费者收到类型不正确的消息时,它会将消息重新路由到无效消息通道

                                                                                                                                              We've seen how to im­ple­ment two classes, Re­questor and Replier (Mes­sage En­d­points), that ex­change a re­quest and reply Mes­sages using Re­quest-Reply. The re­quest mes­sage uses a Return Ad­dress to spe­cify what queue to send the reply on. The reply mes­sage uses a Cor­rel­a­tion Iden­ti­fier to spe­cify which re­quest this is a reply for. The re­questor im­ple­ments a Polling Con­sumer to re­ceive replies, whereas the replier im­ple­ments an Event-Driven Con­sumer to re­ceive re­quests. The re­quest and reply queues are Data­type Chan­nels; when a con­sumer re­ceives a mes­sage that is not of the right type, it reroutes the mes­sage to the In­valid Mes­sage Chan­nel.

                                                                                                                                                JMS 发布-订阅示例

                                                                                                                                                JMS Publish-Subscribe Example

                                                                                                                                                这是一个简单的示例,展示了发布-订阅消息传递的强大功能并探索了可用的替代设计。它展示了如何通过仅发布一次事件来向多个订阅者应用程序通知单个事件,并考虑如何向订阅者传达该事件的详细信息的替代策略。

                                                                                                                                                This is a simple ex­ample that shows the power of pub­lish-sub­scribe mes­saging and ex­plores the al­tern­at­ive designs avail­able. It shows how mul­tiple sub­scriber ap­plic­a­tions can all be in­formed of a single event by pub­lish­ing the event just once and con­siders al­tern­at­ive strategies for how to com­mu­nic­ate de­tails of that event to the sub­scribers.

                                                                                                                                                使用JMS 主题进行发布-订阅

                                                                                                                                                Pub­lish-Sub­scribe Using a JMS Topic

                                                                                                                                                图形/06inf03.gif

                                                                                                                                                要了解简单的发布-订阅通道到底有多大帮助,我们首先需要考虑在多个应用程序之间以分布式方式实现观察者模式是什么样的。在此之前,我们先回顾一下Observer 的基础知识

                                                                                                                                                To un­der­stand how help­ful a simple Pub­lish-Sub­scribe Chan­nel really is, we first need to con­sider what it is like to im­ple­ment the Ob­server pat­tern in a dis­trib­uted fash­ion among mul­tiple ap­plic­a­tions. Before we get to that, let's review the basics of Ob­server.

                                                                                                                                                观察者模式

                                                                                                                                                The Ob­server Pat­tern

                                                                                                                                                观察者模式[ GoF]记录了一种设计,通过该设计,对象可以将更改通知其依赖者,同时保持该对象与其依赖者解耦,以便该对象无论有多少依赖者都可以正常工作,即使它没有依赖者。全部。它的参与者是一个主题(Subject)——宣布其状态变化的对象;以及观察者(Observers)——对接收主题变化通知感兴趣的对象。当主体的状态发生变化时,它会调用其Notify()方法,该方法的实现知道观察者列表并对每个观察者调用Update() 。一些观察者可能对这种状态变化不感兴趣,但是那些感兴趣的观察者可以通过调用来找出新状态是什么GetState()关于这个主题。主体还必须实现观察者用来注册兴趣的Attach(Observer)Detach(Observer)方法。

                                                                                                                                                The Ob­server pat­tern [GoF] doc­u­ments a design through which an object can notify its de­pend­ents of a change, while keep­ing that object de­coupled from its de­pend­ents so that the object works just fine no matter how many de­pend­ents it has, even if it has none at all. Its par­ti­cipants are a Sub­jectthe object an­noun­cing changes in its stateand Ob­server­sob­jects in­ter­ested in re­ceiv­ing no­ti­fic­a­tion of changes in the Sub­ject. When a sub­ject's state changes, it calls its Notify() method, whose im­ple­ment­a­tion knows the list of ob­serv­ers and calls Update() on each of them. Some ob­serv­ers may not be in­ter­ested in this state change, but those that are can find out what the new state is by call­ing Get­State() on the sub­ject. The sub­ject must also im­ple­ment Attach(Ob­server) and Detach(Ob­server) meth­ods that the ob­serv­ers use to re­gister in­terest.

                                                                                                                                                观察者提供了两种从主体向观察者获取新状态的方式:推模型和拉模型。使用推送模型,对每个观察者的Update调用都包含新状态作为参数。因此,感兴趣的观察者可以避免调用GetState(),但是将数据传递给不感兴趣的观察者会浪费精力。相反的方法是拉模型,其中主体发送基本通知,每个观察者向主体请求新状态。因此,每个观察者都可以请求它想要的确切细节,甚至根本没有,但主体通常必须满足对同一数据的多个请求。推送模型需要一种单向通信——主体将数据推送给观察者作为更新的一部分。拉模型需要三种单向通信:主体通知观察者,观察者向主体请求当前状态,主体将当前状态发送给观察者。正如我们将看到的,单向通信的数量会影响通知的设计时复杂性和运行时性能。

                                                                                                                                                Ob­server provides two ways to get the new state from the sub­ject to the ob­server: the push model and the pull model. With the push model, the Update call to each ob­server con­tains the new state as a para­meter. Thus, in­ter­ested ob­serv­ers can avoid having to call Get­State(), but effort is wasted passing data to un­in­ter­ested ob­serv­ers. The op­pos­ite ap­proach is the pull model, where the sub­ject sends basic no­ti­fic­a­tion and each ob­server re­quests the new state from the sub­ject. Thus, each ob­server can re­quest the exact de­tails it wants, even none at all, but the sub­ject often has to serve mul­tiple re­quests for the same data. The push model re­quires a single, one-way com­mu­nic­a­tionthe sub­ject pushes the data to an ob­server as part of the update. The pull model re­quires three one-way com­mu­nic­a­tion­sthe sub­ject no­ti­fies an ob­server, the ob­server re­quests the cur­rent state from the sub­ject, and the sub­ject sends the cur­rent state to the ob­server. As we'll see, the number of one-way com­mu­nic­a­tions af­fects both the design-time com­plex­ity and the runtime per­form­ance of the no­ti­fic­a­tion.

                                                                                                                                                实现主题的Notify()方法的最简单方法是使用单个线程,但这可能会产生不良的性能影响。单个线程将按顺序一次更新每个观察者,因此位于一长串观察者末尾的观察者可能需要等待很长时间才能更新。此外,一个花费很长时间更新所有观察者的主题并没有完成任何其他事情。更糟糕的是,观察者很可能使用其更新线程通过查询主题的状态并处理新数据来对更新做出反应;这种观察者在更新线程中工作使得更新过程花费更长的时间。

                                                                                                                                                The easi­est way to im­ple­ment a sub­ject's Notify() method is with a single thread, but that can have un­desir­able per­form­ance im­plic­a­tions. A single thread will update each ob­server one at a time, in se­quence, so those at the end of a long list of ob­serv­ers may need to wait a long time for up­dates. Also, a sub­ject that spends a long time up­dat­ing all of its ob­serv­ers isn't ac­com­plish­ing any­thing else. Even worse, an ob­server may well use its update thread to react to the update by query­ing the sub­ject for state and pro­cess­ing the new data; such ob­server work in the update thread makes the update pro­cess take even longer.

                                                                                                                                                因此,实现主题的Notify()方法的更复杂的方法是在其自己的线程中运行每个Update()调用。然后,所有观察者都可以同时更新,并且每个观察者在其更新线程中所做的任何工作都不会延迟其他观察者或主题。缺点是实现多线程和处理线程管理问题更加复杂。

                                                                                                                                                Thus, the more soph­ist­ic­ated way to im­ple­ment a sub­ject's Notify() method is to run each Update() call in its own thread. Then, all ob­serv­ers can be up­dated con­cur­rently, and whatever work each may do in its update thread does not delay the other ob­serv­ers or the sub­ject. The down­side is that im­ple­ment­ing mul­ti­th­read­ing and hand­ling thread-man­age­ment issues is more com­plex.

                                                                                                                                                分布式观察者

                                                                                                                                                Dis­trib­uted Ob­server

                                                                                                                                                观察者模式倾向于假设主体及其观察者都在同一个应用程序中运行。该模式的设计支持分布,其中观察者在与主体分离的内存空间中运行,也可能与彼此分离,但分布需要工作。Update ()GetState()方法以及AttachDetach方法必须可远程访问(请参阅远程过程调用)。因为主体必须能够调用每个观察者,反之亦然,所以每个对象必须运行在某种类型的对象请求代理 (ORB) 环境中,该环境允许远程调用它所包含的对象。由于更新详细信息和状态数据将在内存空间之间传递,因此应用程序必须能够序列化,即编组它们传递的对象。

                                                                                                                                                The Ob­server pat­tern tends to assume that the sub­ject and its ob­serv­ers all run in the same ap­plic­a­tion. The pat­tern's design sup­ports dis­tri­bu­tion, where the ob­serv­ers run in a sep­ar­ate memory space from the sub­ject and per­haps from each other, but the dis­tri­bu­tion takes work. The Update() and Get­State() meth­ods, as well as the Attach and Detach meth­ods, must be made re­motely ac­cess­ible (see Remote Pro­ced­ure In­voc­a­tion). Be­cause the sub­ject must be able to call each ob­server, and vice versa, each object must be run­ning in some type of object re­quest broker (ORB) en­vir­on­ment that allows the ob­jects it con­tains to be in­voked re­motely. Be­cause the update de­tails and state data will be passed between memory spaces, the ap­plic­a­tions must be able to seri­al­ize, that is, mar­shal the ob­jects they are passing.

                                                                                                                                                因此,在分布式环境中实现观察者可能会变得相当复杂。多线程观察者不仅在实现上有些困难,而且使方法可以远程访问并远程调用它们也增加了更多的难度。仅仅通知一些依赖者状态变化就可能需要大量工作。

                                                                                                                                                Thus, im­ple­ment­ing Ob­server in a dis­trib­uted en­vir­on­ment can get rather com­plex. Not only is a mul­ti­th­readed Ob­server some­what dif­fi­cult to im­ple­ment, but making meth­ods re­motely ac­cess­ibleand in­vok­ing them re­motely­adds more dif­fi­culty. It can be a lot of work just to notify some de­pend­ents of state changes.

                                                                                                                                                另一个问题是,远程过程调用仅在调用源、目标以及连接它们的网络都正常工作时才起作用。如果主体宣布更改,而远程观察者未准备好处理通知或与网络断开连接,则观察者将丢失通知。虽然在某些情况下观察者可能在没有通知的情况下正常工作,但在其他情况下,丢失的通知可能会导致观察者与主题不同步,这正是观察者模式旨在防止的问题。

                                                                                                                                                An­other prob­lem is that a Remote Pro­ced­ure In­voc­a­tion only works when the source of the call, the target, and the net­work con­nect­ing them are all work­ing prop­erly. If a sub­ject an­nounces a change and a remote ob­server is not ready to pro­cess the no­ti­fic­a­tion or is dis­con­nec­ted from the net­work, the ob­server loses the no­ti­fic­a­tion. While the ob­server may work fine without the no­ti­fic­a­tion in some cases, in other cases the lost no­ti­fic­a­tion may cause the ob­server to get out of sync with the sub­jectthe very prob­lem the Ob­server pat­tern is de­signed to pre­vent.

                                                                                                                                                与拉动模型相比,分发也更青睐推模型。如前所述,推送需要一种单向通信,而拉动则需要三种通信。当通过 RPC(远程过程调用)实现分发时,推送需要一次调用(Update()),而拉取则需要至少两次调用(Update()GetState())。RPC 比非分布式方法调用有更多的开销,因此推送方法所需的额外调用会很快损害性能。

                                                                                                                                                Dis­tri­bu­tion also favors the push model over the pull model. As dis­cussed earlier, push re­quires a single, one-way com­mu­nic­a­tion, whereas pull re­quires three. When the dis­tri­bu­tion is im­ple­men­ted via RPCs (Remote Pro­ced­ure Calls), push re­quires one call (Update()), whereas pull re­quires at least two calls (Update() and Get­State()). RPCs have more over­head than nondis­trib­uted method in­voc­a­tions, so the extra calls re­quired by the push ap­proach can quickly hurt per­form­ance.

                                                                                                                                                发布-订阅

                                                                                                                                                Pub­lish-Sub­scribe

                                                                                                                                                发布-订阅通道实现了观察者模式,使该模式更易于在分布式应用程序中使用。该模式分三步实施。

                                                                                                                                                A Pub­lish-Sub­scribe Chan­nel im­ple­ments the Ob­server pat­tern, making the pat­tern much easier to use among dis­trib­uted ap­plic­a­tions. The pat­tern is im­ple­men­ted in three steps.

                                                                                                                                                1. 消息系统管理员创建一个发布-订阅通道。(这将在 Java 应用程序中表示为 JMS主题。)

                                                                                                                                                2. The mes­saging system ad­min­is­trator cre­ates a Pub­lish-Sub­scribe Chan­nel. (This will be rep­res­en­ted in Java ap­plic­a­tions as a JMS Topic.)

                                                                                                                                                3. 充当主题的应用程序创建一个TopicPublisher (一种MessageProducer )来在通道上发送消息。

                                                                                                                                                4. The ap­plic­a­tion acting as the sub­ject cre­ates a Top­icPub­lisher (a type of Mes­sage­Pro­du­cer) to send mes­sages on the chan­nel.

                                                                                                                                                5. 每个充当观察者(例如,依赖者)的应用程序都会创建一个TopicSubscriber (一种MessageConsumer )来接收通道上的消息。(这类似于在观察者模式中调用Attach(Observer)方法。)

                                                                                                                                                6. Each of the ap­plic­a­tions acting as an ob­server (e.g., a de­pend­ent) cre­ates a Top­ic­Sub­scriber (a type of Mes­sage­Con­sumer) to re­ceive mes­sages on the chan­nel. (This is ana­log­ous to call­ing the Attach(Ob­server) method in the Ob­server pat­tern.)

                                                                                                                                                这就通过通道在主体和观察者之间建立了联系。现在,每当主题有变化要宣布时,它都会通过发送消息来实现。该通道将确保每个观察者都会收到该消息的副本。

                                                                                                                                                This es­tab­lishes a con­nec­tion between the sub­ject and the ob­serv­ers through the chan­nel. Now, whenever the sub­ject has a change to an­nounce, it does so by send­ing a mes­sage. The chan­nel will ensure that each of the ob­serv­ers re­ceives a copy of this mes­sage.

                                                                                                                                                以下是宣布更改所需的代码的简单示例:

                                                                                                                                                Here is a simple ex­ample of the code needed to an­nounce the change:

                                                                                                                                                导入 javax.jms.Connection;
                                                                                                                                                导入 javax.jms.ConnectionFactory;
                                                                                                                                                导入 javax.jms.Destination;
                                                                                                                                                导入 javax.jms.JMSException;
                                                                                                                                                导入 javax.jms.MessageProducer;
                                                                                                                                                导入javax.jms.Session;
                                                                                                                                                导入 javax.jms.TextMessage;
                                                                                                                                                导入 javax.naming.NamingException;
                                                                                                                                                
                                                                                                                                                公共类SubjectGateway {
                                                                                                                                                
                                                                                                                                                    公共静态最终字符串 UPDATE_TOPIC_NAME = "jms/Update";
                                                                                                                                                    private Connection 连接;
                                                                                                                                                    私人会议;
                                                                                                                                                    私有 MessageProducer updateProducer;
                                                                                                                                                
                                                                                                                                                    受保护的主题网关() {
                                                                                                                                                        极好的();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    公共静态SubjectGateway newGateway()抛出JMSException,NamingException {
                                                                                                                                                        主题网关网关 = new 主题网关();
                                                                                                                                                        gateway.initialize();
                                                                                                                                                        返回网关;
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    protected voidinitialize() 抛出 JMSException、NamingException {
                                                                                                                                                        ConnectionFactory 连接工厂 = JndiUtil.getQueueConnectionFactory();
                                                                                                                                                        连接 = connectionFactory.createConnection();
                                                                                                                                                        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                                                                                                                                                        目标 updateTopic = JndiUtil.getDestination(UPDATE_TOPIC_NAME);
                                                                                                                                                        updateProducer = session.createProducer(updateTopic);
                                                                                                                                                
                                                                                                                                                        连接.start();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    公共无效通知(字符串状态)抛出JMSException {
                                                                                                                                                        TextMessage 消息 = session.createTextMessage(state);
                                                                                                                                                        updateProducer.send(消息);
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    公共无效释放()抛出JMSException {
                                                                                                                                                        如果(连接!=空){
                                                                                                                                                            连接.stop();
                                                                                                                                                            连接.close();
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                }
                                                                                                                                                
                                                                                                                                                import javax.jms.Con­nec­tion;
                                                                                                                                                import javax.jms.Con­nec­tion­Fact­ory;
                                                                                                                                                import javax.jms.Des­tin­a­tion;
                                                                                                                                                import javax.jms.JM­SEx­cep­tion;
                                                                                                                                                import javax.jms.Mes­sage­Pro­du­cer;
                                                                                                                                                import javax.jms.Ses­sion;
                                                                                                                                                import javax.jms.Text­Mes­sage;
                                                                                                                                                import javax.naming.NamingEx­cep­tion;
                                                                                                                                                
                                                                                                                                                public class Sub­ject­G­ate­way {
                                                                                                                                                
                                                                                                                                                    public static final String UP­DATE_TOP­IC_­NAME = "jms/Update";
                                                                                                                                                    private Con­nec­tion con­nec­tion;
                                                                                                                                                    private Ses­sion ses­sion;
                                                                                                                                                    private Mes­sage­Pro­du­cer up­date­Pro­du­cer;
                                                                                                                                                
                                                                                                                                                    pro­tec­ted Sub­ject­G­ate­way() {
                                                                                                                                                        super();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    public static Sub­ject­G­ate­way newG­ate­way() throws JM­SEx­cep­tion, NamingEx­cep­tion {
                                                                                                                                                        Sub­ject­G­ate­way gate­way = new Sub­ject­G­ate­way();
                                                                                                                                                        gate­way.ini­tial­ize();
                                                                                                                                                        return gate­way;
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    pro­tec­ted void ini­tial­ize() throws JM­SEx­cep­tion, NamingEx­cep­tion {
                                                                                                                                                        Con­nec­tion­Fact­ory con­nec­tion­Fact­ory = Jn­di­Util.getQueueCon­nec­tion­Fact­ory();
                                                                                                                                                        con­nec­tion = con­nec­tion­Fact­ory.cre­ate­Con­nec­tion();
                                                                                                                                                        ses­sion = con­nec­tion.cre­ateSes­sion(false, Ses­sion.AUTO_AC­KNOW­LEDGE);
                                                                                                                                                        Des­tin­a­tion up­dat­eTopic = Jn­di­Util.get­Des­tin­a­tion(UP­DATE_TOP­IC_­NAME);
                                                                                                                                                        up­date­Pro­du­cer = ses­sion.cre­ate­Pro­du­cer(up­dat­eTopic);
                                                                                                                                                
                                                                                                                                                        con­nec­tion.start();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    public void notify(String state) throws JM­SEx­cep­tion {
                                                                                                                                                        Text­Mes­sage mes­sage = ses­sion.cre­at­e­Text­Mes­sage(state);
                                                                                                                                                        up­date­Pro­du­cer.send(mes­sage);
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    public void re­lease() throws JM­SEx­cep­tion {
                                                                                                                                                        if (con­nec­tion != null) {
                                                                                                                                                            con­nec­tion.stop();
                                                                                                                                                            con­nec­tion.close();
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                }
                                                                                                                                                

                                                                                                                                                subjectGateway主题(未示出)和消息传递系统之间的消息传递网关。主体创建网关,然后使用它来广播通知。本质上,主体的Notify()方法被实现为调用SubjectGateway.notify(String)。然后,网关通过在更新通道上发送消息来宣布更改。

                                                                                                                                                Sub­ject­G­ate­way is a Mes­saging Gate­way between the sub­ject (not shown) and the mes­saging system. The sub­ject cre­ates the gate­way and then uses it to broad­cast no­ti­fic­a­tions. Es­sen­tially, the sub­ject's Notify() method is im­ple­men­ted to call Sub­ject­G­ate­way.notify(String). The gate­way then an­nounces the change by send­ing a mes­sage on the update chan­nel.

                                                                                                                                                以下是接收更改通知所需的代码示例:

                                                                                                                                                Here is an ex­ample of the code needed to re­ceive the change no­ti­fic­a­tion:

                                                                                                                                                导入 javax.jms.Connection;
                                                                                                                                                导入 javax.jms.ConnectionFactory;
                                                                                                                                                导入 javax.jms.Destination;
                                                                                                                                                导入 javax.jms.JMSException;
                                                                                                                                                导入javax.jms.Message;
                                                                                                                                                导入 javax.jms.MessageConsumer;
                                                                                                                                                导入 javax.jms.MessageListener;
                                                                                                                                                导入javax.jms.Session;
                                                                                                                                                导入 javax.jms.TextMessage;
                                                                                                                                                导入 javax.naming.NamingException;
                                                                                                                                                
                                                                                                                                                公共类 ObserverGateway 实现 MessageListener {
                                                                                                                                                
                                                                                                                                                    公共静态最终字符串 UPDATE_TOPIC_NAME = "jms/Update";
                                                                                                                                                    私人观察员观察员;
                                                                                                                                                    private Connection 连接;
                                                                                                                                                    私人消息消费者更新消费者;
                                                                                                                                                
                                                                                                                                                    受保护的 ObserverGateway() {
                                                                                                                                                        极好的();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    公共静态ObserverGateway newGateway(观察者观察者)
                                                                                                                                                        抛出 JMSException、NamingException {
                                                                                                                                                        ObserverGateway 网关 = new ObserverGateway();
                                                                                                                                                        gateway.initialize(观察者);
                                                                                                                                                        返回网关;
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    protected voidinitialize(观察者观察者)抛出JMSException,NamingException {
                                                                                                                                                        this.observer = 观察者;
                                                                                                                                                
                                                                                                                                                        ConnectionFactory 连接工厂 = JndiUtil.getQueueConnectionFactory();
                                                                                                                                                        连接 = connectionFactory.createConnection();
                                                                                                                                                        会话 session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                                                                                                                                                        目标 updateTopic = JndiUtil.getDestination(UPDATE_TOPIC_NAME);
                                                                                                                                                        updateConsumer = session.createConsumer(updateTopic);
                                                                                                                                                        updateConsumer.setMessageListener(this);
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    公共无效onMessage(消息消息){
                                                                                                                                                        尝试 {
                                                                                                                                                            TextMessage textMsg = (TextMessage) 消息;// 假设强制转换始终有效
                                                                                                                                                            String newState = textMsg.getText();
                                                                                                                                                            更新(新状态);
                                                                                                                                                        } catch (JMSException e) {
                                                                                                                                                            e.printStackTrace();
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    公共无效附加()抛出JMSException {
                                                                                                                                                        连接.start();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    公共无效分离()抛出JMSException {
                                                                                                                                                        如果(连接!=空){
                                                                                                                                                            连接.stop();
                                                                                                                                                            连接.close();
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    私有无效更新(字符串newState)抛出JMSException {
                                                                                                                                                        观察者.更新(newState);
                                                                                                                                                    }
                                                                                                                                                }
                                                                                                                                                
                                                                                                                                                import javax.jms.Con­nec­tion;
                                                                                                                                                import javax.jms.Con­nec­tion­Fact­ory;
                                                                                                                                                import javax.jms.Des­tin­a­tion;
                                                                                                                                                import javax.jms.JM­SEx­cep­tion;
                                                                                                                                                import javax.jms.Mes­sage;
                                                                                                                                                import javax.jms.Mes­sage­Con­sumer;
                                                                                                                                                import javax.jms.Mes­sageL­istener;
                                                                                                                                                import javax.jms.Ses­sion;
                                                                                                                                                import javax.jms.Text­Mes­sage;
                                                                                                                                                import javax.naming.NamingEx­cep­tion;
                                                                                                                                                
                                                                                                                                                public class Ob­server­Gate­way im­ple­ments Mes­sageL­istener {
                                                                                                                                                
                                                                                                                                                    public static final String UP­DATE_TOP­IC_­NAME = "jms/Update";
                                                                                                                                                    private Ob­server ob­server;
                                                                                                                                                    private Con­nec­tion con­nec­tion;
                                                                                                                                                    private Mes­sage­Con­sumer up­date­Con­sumer;
                                                                                                                                                
                                                                                                                                                    pro­tec­ted Ob­server­Gate­way() {
                                                                                                                                                        super();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    public static Ob­server­Gate­way newG­ate­way(Ob­server ob­server)
                                                                                                                                                        throws JM­SEx­cep­tion, NamingEx­cep­tion {
                                                                                                                                                        Ob­server­Gate­way gate­way = new Ob­server­Gate­way();
                                                                                                                                                        gate­way.ini­tial­ize(ob­server);
                                                                                                                                                        return gate­way;
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    pro­tec­ted void ini­tial­ize(Ob­server ob­server) throws JM­SEx­cep­tion, NamingEx­cep­tion {
                                                                                                                                                        this.ob­server = ob­server;
                                                                                                                                                
                                                                                                                                                        Con­nec­tion­Fact­ory con­nec­tion­Fact­ory = Jn­di­Util.getQueueCon­nec­tion­Fact­ory();
                                                                                                                                                        con­nec­tion = con­nec­tion­Fact­ory.cre­ate­Con­nec­tion();
                                                                                                                                                        Ses­sion ses­sion = con­nec­tion.cre­ateSes­sion(false, Ses­sion.AUTO_AC­KNOW­LEDGE);
                                                                                                                                                        Des­tin­a­tion up­dat­eTopic = Jn­di­Util.get­Des­tin­a­tion(UP­DATE_TOP­IC_­NAME);
                                                                                                                                                        up­date­Con­sumer = ses­sion.cre­ate­Con­sumer(up­dat­eTopic);
                                                                                                                                                        up­date­Con­sumer.set­Mes­sageL­istener(this);
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    public void on­Mes­sage(Mes­sage mes­sage) {
                                                                                                                                                        try {
                                                                                                                                                            Text­Mes­sage textMsg = (Text­Mes­sage) mes­sage; // assume cast always works
                                                                                                                                                            String newS­tate = textMsg.get­Text();
                                                                                                                                                            update(newS­tate);
                                                                                                                                                        } catch (JM­SEx­cep­tion e) {
                                                                                                                                                            e.print­Stack­Trace();
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    public void attach() throws JM­SEx­cep­tion {
                                                                                                                                                        con­nec­tion.start();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    public void detach() throws JM­SEx­cep­tion {
                                                                                                                                                        if (con­nec­tion != null) {
                                                                                                                                                            con­nec­tion.stop();
                                                                                                                                                            con­nec­tion.close();
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    private void update(String newS­tate) throws JM­SEx­cep­tion {
                                                                                                                                                        ob­server.update(newS­tate);
                                                                                                                                                    }
                                                                                                                                                }
                                                                                                                                                

                                                                                                                                                ObserverGateway 是另一个消息传递网关,这次位于观察者(未显示)和消息传递系统之间。观察者创建网关,然后使用Attach()启动连接(类似于在观察者模式中调用Attach(Observer)方法)。网关是一个事件驱动的消费者,因此它实现了MessageListener接口,该接口需要onMessage方法。这样,当收到更新时,网关会处理消息以获取新状态并调用自己的update(String)方法,然后调用观察者中相应的消息。

                                                                                                                                                Ob­server­Gate­way is an­other Mes­saging Gate­way, this time between the ob­server (not shown) and the mes­saging system. The ob­server cre­ates the gate­way, then uses attach() to start the con­nec­tion (which is ana­log­ous to call­ing the Attach(Ob­server) method in the Ob­server pat­tern). The gate­way is an Event-Driven Con­sumer, so it im­ple­ments the Mes­sageL­istener in­ter­face, which re­quires the on­Mes­sage method. In this way, when an update is re­ceived, the gate­way pro­cesses the mes­sage to get the new state and calls its own update(String) method, which then calls the cor­res­pond­ing mes­sage in the ob­server.

                                                                                                                                                这两个类实现了 Observer 的推送模型版本。 通过SubjectGateway.notify(String)发送的通知消息,消息的存在告诉观察者发生了变化,但消息的内容告诉观察者主题的新状态是什么。新的状态被从主体推向观察者。正如我们稍后将看到的,还有另一种使用拉模型来实现此功能的方法。

                                                                                                                                                These two classes im­ple­ment the push model ver­sion of Ob­server. With the no­ti­fic­a­tion mes­sage sent by Sub­ject­G­ate­way.notify(String), the ex­ist­ence of the mes­sage tells the ob­server that a change has oc­curred, but it is the con­tents of the mes­sage that tell the ob­server what the sub­ject's new state is. The new state is being pushed from the sub­ject to the ob­server. As we'll see later, there's an­other way to im­ple­ment this func­tion­al­ity using the pull model.

                                                                                                                                                比较

                                                                                                                                                Com­par­is­ons

                                                                                                                                                对于应用程序之间的分布式通知,发布-订阅(例如消息传递)方法比实现Observer 的传统同步(例如 RPC)方法具有多个优点

                                                                                                                                                For dis­trib­uted no­ti­fic­a­tion between ap­plic­a­tions, the pub­lish-sub­scribe (e.g., mes­saging) ap­proach has sev­eral ad­vant­ages over the tra­di­tional, syn­chron­ous (e.g., RPC) ap­proach of im­ple­ment­ing Ob­server.

                                                                                                                                                • 简化通知。主题的Notify()实现变得异常简单;代码只需在通道上发送消息即可。同样,Observer.Update()只需接收一条消息。

                                                                                                                                                • Sim­pli­fies no­ti­fic­a­tion. The sub­ject's im­ple­ment­a­tion of Notify() be­comes in­cred­ibly simple; the code just has to send a mes­sage on a chan­nel. Like­wise, Ob­server.Update() just has to re­ceive a mes­sage.

                                                                                                                                                • 简化附加/分离。观察者需要订阅和取消订阅频道,而不是附加到主题或从主题分离。主体不需要实现Attach(Observer)Detach(Observer)(尽管观察者可以实现这些方法来封装订阅和取消订阅行为)。

                                                                                                                                                • Sim­pli­fies attach/detach. Rather than attach to and detach from the sub­ject, an ob­server needs to sub­scribe to and un­sub­scribe from the chan­nel. The sub­ject does not need to im­ple­ment Attach(Ob­server) or Detach(Ob­server) (al­though the ob­server may im­ple­ment these meth­ods to en­cap­su­late the sub­scribe and un­sub­scribe be­ha­vior).

                                                                                                                                                • 简化并发线程。主体只需要一个线程来同时更新所有观察者,通道同时向观察者传递通知消息,每个观察者在自己的线程中处理更新。这简化了主体的实现,并且因为每个观察者都使用自己的线程,所以一个观察者在其更新线程中所做的事情不会影响其他观察者。

                                                                                                                                                • Sim­pli­fies con­cur­rent thread­ing. The sub­ject needs only one thread to update all ob­serv­ers con­cur­rentlythe chan­nel de­liv­ers the no­ti­fic­a­tion mes­sage to the ob­serv­ers con­cur­rently­and each ob­server handles the update in its own thread. This sim­pli­fies the sub­ject's im­ple­ment­a­tion, and be­cause each ob­server uses its own thread, what one does in its update thread does not affect the others.

                                                                                                                                                • 简化远程访问。主体和观察者都不需要实现任何远程方法,也不需要在 ORB 中运行。他们只需要访问消息传递系统,它就会处理分发。

                                                                                                                                                • Sim­pli­fies remote access. Neither the sub­ject nor the ob­serv­ers have to im­ple­ment any remote meth­ods, nor do they need to run in an ORB. They just need to access the mes­saging system, and it handles the dis­tri­bu­tion.

                                                                                                                                                • 提高可靠性。由于通道使用消息传递,通知将排队直到观察者可以处理它们,这也使观察者能够限制通知。如果观察者想要接收在观察者断开连接时发送的通知,它应该使自己成为持久订阅者

                                                                                                                                                • In­creases re­li­ab­il­ity. Be­cause the chan­nel uses mes­saging, no­ti­fic­a­tions will be queued until the ob­server can pro­cess them, which also en­ables the ob­server to throttle the no­ti­fic­a­tions. If an ob­server wants to re­ceive no­ti­fic­a­tions that are sent while that ob­server is dis­con­nec­ted, it should make itself a Dur­able Sub­scriber.

                                                                                                                                                发布-订阅方法没有改变的一个问题是序列化。无论观察者是通过RPC还是消息传递实现的,状态数据都是从主体的内存空间分发到每个观察者的内存空间,因此数据必须被序列化(即编组)。无论哪种方法都必须实现此行为。

                                                                                                                                                One issue that the pub­lish-sub­scribe ap­proach does not change is seri­al­iz­a­tion. Whether Ob­server is im­ple­men­ted through RPC or mes­saging, state data is being dis­trib­uted from the sub­ject's memory space to each ob­server's memory space, so the data has to be seri­al­ized (i.e., mar­shaled). This be­ha­vior has to be im­ple­men­ted for either ap­proach.

                                                                                                                                                如果说发布-订阅方法有一个缺点,那就是该方法需要消息传递,这意味着主题和观察者应用程序必须能够访问共享消息传递系统,并且必须作为该消息传递系统的客户端来实现。尽管如此,将应用程序变成消息传递客户端并不比使用 RPC 方法更困难,甚至可能更容易。

                                                                                                                                                If the pub­lish-sub­scribe ap­proach has a down­side, it's that the ap­proach re­quires mes­saging, which means that the sub­ject and ob­server ap­plic­a­tions must have access to a shared mes­saging system and must be im­ple­men­ted as cli­ents of that mes­saging system. Still, making ap­plic­a­tions into mes­saging cli­ents is no more dif­fi­cult, and prob­ably easier, than using the RPC ap­proach.

                                                                                                                                                推拉模型

                                                                                                                                                Push and Pull Models

                                                                                                                                                发布-订阅方法的另一个潜在缺点是拉模型比推模型更复杂。如前所述,拉模型比推模型需要更多的来回讨论。当讨论是在分布式应用程序之间进行时,额外的通信会严重损害性能。

                                                                                                                                                An­other po­ten­tial down­side of the pub­lish-sub­scribe ap­proach is that the pull model is more com­plex than the push model. As dis­cussed earlier, the pull model re­quires more back-and-forth dis­cus­sion than the push model. When the dis­cus­sion is among dis­trib­uted ap­plic­a­tions, the extra com­mu­nic­a­tion can sig­ni­fic­antly hurt per­form­ance.

                                                                                                                                                消息传递的通信比 RPC 更复杂。在这两种情况下,Update()都是一种单向通信,要么是返回 void 的 RPC,要么是从主体到观察者的单个事件消息。更棘手的部分是当观察者需要查询主体的状态时。GetState()是双向通信,可以是请求状态并返回状态的单个 RPC,也可以是请求-应答一对消息,其中命令消息请求状态并单独的文档消息返回状态。

                                                                                                                                                The com­mu­nic­a­tion is more com­plex with mes­saging than with RPC. In both cases, Update() is a one-way com­mu­nic­a­tion, either an RPC that re­turns void or a single Event Mes­sage from the sub­ject to the ob­server. The trick­ier part is when an ob­server needs to query the sub­ject's state. Get­State() is a two-way com­mu­nic­a­tion, either a single RPC that re­quests the state and re­turns it, or a Re­quest-Reply a pair of mes­sages where a Com­mand Mes­sage re­quests the state and a sep­ar­ate Doc­u­ment Mes­sage re­turns it.

                                                                                                                                                是什么构成了请求-回复更困难的不仅是它需要一对消息,而且还需要一对通道来传输这些消息。其中一个通道是“获取状态请求”通道,从观察者到主体;观察者在该通道上发送状态请求。另一个通道,即获取状态回复通道,从主体返回到观察者;主体在该通道上发送状态回复。所有观察者都可以共享相同的请求通道,但他们可能每个都需要自己的回复通道。每个观察者不仅需要接收任何响应,还需要接收其特定请求的特定响应,确保这一点的最简单方法是每个观察者都有自己的回复通道。(另一种方法是使用单个回复通道并使用相关标识符来确定

                                                                                                                                                What makes Re­quest-Reply more dif­fi­cult is not just that it re­quires a pair of mes­sages, but that it re­quires a pair of chan­nels to trans­mit those mes­sages. One chan­nel, the get-state-re­quest chan­nel, goes from an ob­server to the sub­ject; an ob­server sends the state re­quest on that chan­nel. The other chan­nel, the get-state-reply chan­nel, goes from the sub­ject back to the ob­server; the sub­ject sends the state reply on that chan­nel. All of the ob­serv­ers can share the same re­quest chan­nel, but they will prob­ably each need their own reply chan­nel. Each ob­server needs to re­ceive not just any re­sponse but the par­tic­u­lar re­sponse for its spe­cific re­quest, and the easi­est way to ensure this is for each ob­server to have its own reply chan­nel. (An al­tern­at­ive is to use a single reply chan­nel and use Cor­rel­a­tion Iden­ti­fi­ers to figure out which reply goes to which ob­server, but a sep­ar­ate chan­nel per ob­server is a lot easier to im­ple­ment.)

                                                                                                                                                使用拉模型发布-订阅

                                                                                                                                                Pub­lish-Sub­scribe Using the Pull Model

                                                                                                                                                图形/06inf04.gif

                                                                                                                                                每个观察者的回复通道可能会导致通道爆炸。如此大量的通道可能是可以管理的,但是当必须使用这些通道的观察者数量在运行时动态变化时,消息传递系统管理员不知道要创建多少个静态通道。即使有足够的通道供所有观察者使用,每个观察者如何知道使用哪个通道?

                                                                                                                                                A reply chan­nel for each ob­server can lead to an ex­plo­sion of chan­nels. Such a large number of chan­nels may be man­age­able, but the mes­saging system ad­min­is­trator does not know how many static chan­nels to create when the number of ob­serv­ers that must use these chan­nels changes dy­nam­ic­ally at runtime. Even if there are enough chan­nels for all of the ob­serv­ers, how does each ob­server know which chan­nel to use?

                                                                                                                                                JMS 有一个功能TemporaryQueue ,专门用于此目的 [ Hapner ] (另请参阅Request-Reply的讨论。)观察者可以创建一个专门供自己使用的临时队列,将该队列指定为其请求中的返回地址,并等待该队列上的回复。频繁创建新队列可能效率低下,具体取决于消息传递系统的实现,并且临时队列无法持久化(与保证交付一起使用) 。但是,如果您不想使用推送模型,则可以使用临时队列来实现拉取模型。

                                                                                                                                                JMS has a fea­ture, Tem­por­aryQueue, spe­cific­ally for this pur­pose [Hapner]. (Also see the dis­cus­sion of Re­quest-Reply.) An ob­server can create a tem­por­ary queue ex­clus­ively for its own use, spe­cify that queue as the Return Ad­dress in its re­quest, and wait for the reply on that queue. Cre­at­ing new queues fre­quently can be in­ef­fi­cient, de­pend­ing on your mes­saging system's im­ple­ment­a­tion, and tem­por­ary queues cannot be per­sist­ent (for use with Guar­an­teed De­liv­ery). How­ever, if you don't want to use the push model, you can im­ple­ment the pull model using tem­por­ary queues.

                                                                                                                                                这两个类展示了如何使用拉模型实现网关。

                                                                                                                                                These two classes show how to im­ple­ment the gate­ways using the pull model.

                                                                                                                                                导入 javax.jms.Connection;
                                                                                                                                                导入 javax.jms.ConnectionFactory;
                                                                                                                                                导入 javax.jms.Destination;
                                                                                                                                                导入 javax.jms.JMSException;
                                                                                                                                                导入javax.jms.Message;
                                                                                                                                                导入 javax.jms.MessageConsumer;
                                                                                                                                                导入 javax.jms.MessageListener;
                                                                                                                                                导入 javax.jms.MessageProducer;
                                                                                                                                                导入javax.jms.Session;
                                                                                                                                                导入 javax.jms.TextMessage;
                                                                                                                                                导入 javax.naming.NamingException;
                                                                                                                                                
                                                                                                                                                公共类 PullSubjectGateway {
                                                                                                                                                
                                                                                                                                                    公共静态最终字符串 UPDATE_TOPIC_NAME = "jms/Update";
                                                                                                                                                    私人 PullSubject 主题;
                                                                                                                                                    private Connection 连接;
                                                                                                                                                    私人会议;
                                                                                                                                                    私有 MessageProducer updateProducer;
                                                                                                                                                
                                                                                                                                                    受保护的 PullSubjectGateway() {
                                                                                                                                                        极好的();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    公共静态 PullSubjectGateway newGateway(PullSubject 主题)
                                                                                                                                                        抛出 JMSException、NamingException {
                                                                                                                                                        PullSubjectGateway 网关 = new PullSubjectGateway();
                                                                                                                                                        gateway.initialize(主题);
                                                                                                                                                        返回网关;
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    protected voidinitialize(PullSubject subject) 抛出 JMSException, NamingException {
                                                                                                                                                        this.subject = 主题;
                                                                                                                                                
                                                                                                                                                        ConnectionFactory 连接工厂 = JndiUtil.getQueueConnectionFactory();
                                                                                                                                                        连接 = connectionFactory.createConnection();
                                                                                                                                                        session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                                                                                                                                                        目标 updateTopic = JndiUtil.getDestination(UPDATE_TOPIC_NAME);
                                                                                                                                                        updateProducer = session.createProducer(updateTopic);
                                                                                                                                                
                                                                                                                                                        new Thread(new GetStateReplier()).start();
                                                                                                                                                
                                                                                                                                                        连接.start();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    公共无效notifyNoState()抛出JMSException {
                                                                                                                                                        TextMessage 消息 = session.createTextMessage();
                                                                                                                                                        updateProducer.send(消息);
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    公共无效释放()抛出JMSException {
                                                                                                                                                        如果(连接!=空){
                                                                                                                                                            连接.stop();
                                                                                                                                                            连接.close();
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    私有类 GetStateReplier 实现 Runnable、MessageListener {
                                                                                                                                                
                                                                                                                                                        公共静态最终字符串 GET_STATE_QUEUE_NAME = "jms/GetState";
                                                                                                                                                        私人会议;
                                                                                                                                                        私有 MessageConsumer 请求消费者;
                                                                                                                                                
                                                                                                                                                        公共无效运行(){
                                                                                                                                                            尝试 {
                                                                                                                                                                session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                                                                                                                                                                目标 getStateQueue = JndiUtil.getDestination(GET_STATE_QUEUE_NAME);
                                                                                                                                                                requestConsumer = session.createConsumer(getStateQueue);
                                                                                                                                                                requestConsumer.setMessageListener(this);
                                                                                                                                                            } catch (异常 e) {
                                                                                                                                                                e.printStackTrace();
                                                                                                                                                            }
                                                                                                                                                        }
                                                                                                                                                
                                                                                                                                                        公共无效onMessage(消息消息){
                                                                                                                                                            尝试 {
                                                                                                                                                                目标回复队列 = message.getJMSReplyTo();
                                                                                                                                                                MessageProducer 回复生产者 = session.createProducer(replyQueue);
                                                                                                                                                
                                                                                                                                                                消息回复Message = session.createTextMessage(subject.getState());
                                                                                                                                                                回复Producer.send(replyMessage);
                                                                                                                                                            } catch (JMSException e) {
                                                                                                                                                                e.printStackTrace();
                                                                                                                                                            }
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                }
                                                                                                                                                
                                                                                                                                                import javax.jms.Con­nec­tion;
                                                                                                                                                import javax.jms.Con­nec­tion­Fact­ory;
                                                                                                                                                import javax.jms.Des­tin­a­tion;
                                                                                                                                                import javax.jms.JM­SEx­cep­tion;
                                                                                                                                                import javax.jms.Mes­sage;
                                                                                                                                                import javax.jms.Mes­sage­Con­sumer;
                                                                                                                                                import javax.jms.Mes­sageL­istener;
                                                                                                                                                import javax.jms.Mes­sage­Pro­du­cer;
                                                                                                                                                import javax.jms.Ses­sion;
                                                                                                                                                import javax.jms.Text­Mes­sage;
                                                                                                                                                import javax.naming.NamingEx­cep­tion;
                                                                                                                                                
                                                                                                                                                public class Pull­Sub­ject­G­ate­way {
                                                                                                                                                
                                                                                                                                                    public static final String UP­DATE_TOP­IC_­NAME = "jms/Update";
                                                                                                                                                    private Pull­Sub­ject sub­ject;
                                                                                                                                                    private Con­nec­tion con­nec­tion;
                                                                                                                                                    private Ses­sion ses­sion;
                                                                                                                                                    private Mes­sage­Pro­du­cer up­date­Pro­du­cer;
                                                                                                                                                
                                                                                                                                                    pro­tec­ted Pull­Sub­ject­G­ate­way() {
                                                                                                                                                        super();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    public static Pull­Sub­ject­G­ate­way newG­ate­way(Pull­Sub­ject sub­ject)
                                                                                                                                                        throws JM­SEx­cep­tion, NamingEx­cep­tion {
                                                                                                                                                        Pull­Sub­ject­G­ate­way gate­way = new Pull­Sub­ject­G­ate­way();
                                                                                                                                                        gate­way.ini­tial­ize(sub­ject);
                                                                                                                                                        return gate­way;
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    pro­tec­ted void ini­tial­ize(Pull­Sub­ject sub­ject) throws JM­SEx­cep­tion, NamingEx­cep­tion {
                                                                                                                                                        this.sub­ject = sub­ject;
                                                                                                                                                
                                                                                                                                                        Con­nec­tion­Fact­ory con­nec­tion­Fact­ory = Jn­di­Util.getQueueCon­nec­tion­Fact­ory();
                                                                                                                                                        con­nec­tion = con­nec­tion­Fact­ory.cre­ate­Con­nec­tion();
                                                                                                                                                        ses­sion = con­nec­tion.cre­ateSes­sion(false, Ses­sion.AUTO_AC­KNOW­LEDGE);
                                                                                                                                                        Des­tin­a­tion up­dat­eTopic = Jn­di­Util.get­Des­tin­a­tion(UP­DATE_TOP­IC_­NAME);
                                                                                                                                                        up­date­Pro­du­cer = ses­sion.cre­ate­Pro­du­cer(up­dat­eTopic);
                                                                                                                                                
                                                                                                                                                        new Thread(new Get­StateReplier()).start();
                                                                                                                                                
                                                                                                                                                        con­nec­tion.start();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    public void no­ti­fyNoState() throws JM­SEx­cep­tion {
                                                                                                                                                        Text­Mes­sage mes­sage = ses­sion.cre­at­e­Text­Mes­sage();
                                                                                                                                                        up­date­Pro­du­cer.send(mes­sage);
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    public void re­lease() throws JM­SEx­cep­tion {
                                                                                                                                                        if (con­nec­tion != null) {
                                                                                                                                                            con­nec­tion.stop();
                                                                                                                                                            con­nec­tion.close();
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    private class Get­StateReplier im­ple­ments Run­nable, Mes­sageL­istener {
                                                                                                                                                
                                                                                                                                                        public static final String GET_STATE_QUEUE_­NAME = "jms/Get­State";
                                                                                                                                                        private Ses­sion ses­sion;
                                                                                                                                                        private Mes­sage­Con­sumer re­quest­Con­sumer;
                                                                                                                                                
                                                                                                                                                        public void run() {
                                                                                                                                                            try {
                                                                                                                                                                ses­sion = con­nec­tion.cre­ateSes­sion(false, Ses­sion.AUTO_AC­KNOW­LEDGE);
                                                                                                                                                                Des­tin­a­tion get­StateQueue = Jn­di­Util.get­Des­tin­a­tion(GET_STATE_QUEUE_­NAME);
                                                                                                                                                                re­quest­Con­sumer = ses­sion.cre­ate­Con­sumer(get­StateQueue);
                                                                                                                                                                re­quest­Con­sumer.set­Mes­sageL­istener(this);
                                                                                                                                                            } catch (Ex­cep­tion e) {
                                                                                                                                                                e.print­Stack­Trace();
                                                                                                                                                            }
                                                                                                                                                        }
                                                                                                                                                
                                                                                                                                                        public void on­Mes­sage(Mes­sage mes­sage) {
                                                                                                                                                            try {
                                                                                                                                                                Des­tin­a­tion replyQueue = mes­sage.getJM­S­ReplyTo();
                                                                                                                                                                Mes­sage­Pro­du­cer replyPro­du­cer = ses­sion.cre­ate­Pro­du­cer(replyQueue);
                                                                                                                                                
                                                                                                                                                                Mes­sage replyMes­sage = ses­sion.cre­at­e­Text­Mes­sage(sub­ject.get­State());
                                                                                                                                                                replyPro­du­cer.send(replyMes­sage);
                                                                                                                                                            } catch (JM­SEx­cep­tion e) {
                                                                                                                                                                e.print­Stack­Trace();
                                                                                                                                                            }
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                }
                                                                                                                                                

                                                                                                                                                PullSubjectGateway 与SubjectGateway 非常相似。 拉取版本现在具有对其主题的引用,因此网关可以在观察者请求时查询主题的状态。notify(String)现在已变为notifyNoState(),因为拉取模型只是发送通知而不包含任何状态(并且因为 Java 已经使用方法名称notify())。

                                                                                                                                                Pull­Sub­ject­G­ate­way is very sim­ilar to Sub­ject­G­ate­way. The pull ver­sion now has a ref­er­ence to its sub­ject, so the gate­way can query the sub­ject for its state when re­ques­ted by an ob­server. notify(String) has now become no­ti­fyNoState(), be­cause the pull model simply sends out no­ti­fic­a­tion without in­clud­ing any state (and be­cause Java already uses the method name notify()).

                                                                                                                                                拉取模型的一个重要补充是GetStateReplier ,它是一个实现Runnable 的,以便它可以在自己的线程中运行。它也是一个MessageListener ,这使其成为一个事件驱动的 Consumer 。它的onMessage方法从GetState 队列读取请求,并将包含主题状态的回复发送到请求指定的队列。这样,当观察者发出GetState()请求时,网关会发送回复(请参阅Request-Reply)。

                                                                                                                                                The big ad­di­tion for the pull model is Get­StateReplier, an inner class that im­ple­ments Run­nable so that it can run in its own thread. It is also a Mes­sageL­istener, which makes it an Event-Driven Con­sumer. Its on­Mes­sage method reads re­quests from the Get­State queue and sends replies con­tain­ing the sub­ject's state to the queue spe­cified by the re­quest. In this way, when an ob­server makes a Get­State() re­quest, the gate­way sends a reply (see Re­quest-Reply).

                                                                                                                                                导入 javax.jms.Destination;
                                                                                                                                                导入 javax.jms.JMSException;
                                                                                                                                                导入javax.jms.Message;
                                                                                                                                                导入 javax.jms.MessageConsumer;
                                                                                                                                                导入 javax.jms.MessageListener;
                                                                                                                                                导入javax.jms.Queue;
                                                                                                                                                导入 javax.jms.QueueConnection;
                                                                                                                                                导入 javax.jms.QueueConnectionFactory;
                                                                                                                                                导入 javax.jms.QueueRequestor;
                                                                                                                                                导入 javax.jms.QueueSession;
                                                                                                                                                导入javax.jms.Session;
                                                                                                                                                导入 javax.jms.TextMessage;
                                                                                                                                                导入 javax.naming.NamingException;
                                                                                                                                                
                                                                                                                                                公共类 PullObserverGateway 实现 MessageListener {
                                                                                                                                                
                                                                                                                                                    公共静态最终字符串 UPDATE_TOPIC_NAME = "jms/Update";
                                                                                                                                                    公共静态最终字符串 GET_STATE_QUEUE_NAME = "jms/GetState";
                                                                                                                                                    私有 PullObserver 观察者;
                                                                                                                                                    私有 QueueConnection 连接;
                                                                                                                                                    私有 QueueSession 会话;
                                                                                                                                                    私人消息消费者更新消费者;
                                                                                                                                                    私有 QueueRequestor getStateRequestor;
                                                                                                                                                
                                                                                                                                                    受保护的 PullObserverGateway() {
                                                                                                                                                        极好的();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    公共静态 PullObserverGateway newGateway(PullObserver 观察者)
                                                                                                                                                        抛出 JMSException、NamingException {
                                                                                                                                                        PullObserverGateway 网关 = new PullObserverGateway();
                                                                                                                                                        gateway.initialize(观察者);
                                                                                                                                                        返回网关;
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    protected voidinitialize(PullObserver观察者)抛出JMSException,NamingException {
                                                                                                                                                        this.observer = 观察者;
                                                                                                                                                
                                                                                                                                                        QueueConnectionFactory 连接工厂 = JndiUtil.getQueueConnectionFactory();
                                                                                                                                                        连接 = connectionFactory.createQueueConnection();
                                                                                                                                                        session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
                                                                                                                                                        目标 updateTopic = JndiUtil.getDestination(UPDATE_TOPIC_NAME);
                                                                                                                                                        updateConsumer = session.createConsumer(updateTopic);
                                                                                                                                                        updateConsumer.setMessageListener(this);
                                                                                                                                                
                                                                                                                                                        队列 getStateQueue = (Queue) JndiUtil.getDestination(GET_STATE_QUEUE_NAME);
                                                                                                                                                        getStateRequestor = new QueueRequestor(会话, getStateQueue);
                                                                                                                                                
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    公共无效onMessage(消息消息){
                                                                                                                                                        尝试 {
                                                                                                                                                            // 消息内容为空
                                                                                                                                                            updateNoState();
                                                                                                                                                        } catch (JMSException e) {
                                                                                                                                                            e.printStackTrace();
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    公共无效附加()抛出JMSException {
                                                                                                                                                        连接.start();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    公共无效分离()抛出JMSException {
                                                                                                                                                        如果(连接!=空){
                                                                                                                                                            连接.stop();
                                                                                                                                                            连接.close();
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    私有无效 updateNoState() 抛出 JMSException {
                                                                                                                                                        TextMessage getStateRequestMessage = session.createTextMessage();
                                                                                                                                                        消息 getStateReplyMessage = getStateRequestor.request(getStateRequestMessage);
                                                                                                                                                        TextMessage textMsg = (TextMessage) getStateReplyMessage; // 假设强制转换始终有效
                                                                                                                                                        String newState = textMsg.getText();
                                                                                                                                                        观察者.更新(newState);
                                                                                                                                                    }
                                                                                                                                                }
                                                                                                                                                
                                                                                                                                                import javax.jms.Des­tin­a­tion;
                                                                                                                                                import javax.jms.JM­SEx­cep­tion;
                                                                                                                                                import javax.jms.Mes­sage;
                                                                                                                                                import javax.jms.Mes­sage­Con­sumer;
                                                                                                                                                import javax.jms.Mes­sageL­istener;
                                                                                                                                                import javax.jms.Queue;
                                                                                                                                                import javax.jms.QueueCon­nec­tion;
                                                                                                                                                import javax.jms.QueueCon­nec­tion­Fact­ory;
                                                                                                                                                import javax.jms.QueueRe­questor;
                                                                                                                                                import javax.jms.QueueSes­sion;
                                                                                                                                                import javax.jms.Ses­sion;
                                                                                                                                                import javax.jms.Text­Mes­sage;
                                                                                                                                                import javax.naming.NamingEx­cep­tion;
                                                                                                                                                
                                                                                                                                                public class Pul­lOb­server­Gate­way im­ple­ments Mes­sageL­istener {
                                                                                                                                                
                                                                                                                                                    public static final String UP­DATE_TOP­IC_­NAME = "jms/Update";
                                                                                                                                                    public static final String GET_STATE_QUEUE_­NAME = "jms/Get­State";
                                                                                                                                                    private Pul­lOb­server ob­server;
                                                                                                                                                    private QueueCon­nec­tion con­nec­tion;
                                                                                                                                                    private QueueSes­sion ses­sion;
                                                                                                                                                    private Mes­sage­Con­sumer up­date­Con­sumer;
                                                                                                                                                    private QueueRe­questor get­State­Re­questor;
                                                                                                                                                
                                                                                                                                                    pro­tec­ted Pul­lOb­server­Gate­way() {
                                                                                                                                                        super();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    public static Pul­lOb­server­Gate­way newG­ate­way(Pul­lOb­server ob­server)
                                                                                                                                                        throws JM­SEx­cep­tion, NamingEx­cep­tion {
                                                                                                                                                        Pul­lOb­server­Gate­way gate­way = new Pul­lOb­server­Gate­way();
                                                                                                                                                        gate­way.ini­tial­ize(ob­server);
                                                                                                                                                        return gate­way;
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    pro­tec­ted void ini­tial­ize(Pul­lOb­server ob­server) throws JM­SEx­cep­tion, NamingEx­cep­tion {
                                                                                                                                                        this.ob­server = ob­server;
                                                                                                                                                
                                                                                                                                                        QueueCon­nec­tion­Fact­ory con­nec­tion­Fact­ory = Jn­di­Util.getQueueCon­nec­tion­Fact­ory();
                                                                                                                                                        con­nec­tion = con­nec­tion­Fact­ory.cre­ateQueueCon­nec­tion();
                                                                                                                                                        ses­sion = con­nec­tion.cre­ateQueueSes­sion(false, Ses­sion.AUTO_AC­KNOW­LEDGE);
                                                                                                                                                        Des­tin­a­tion up­dat­eTopic = Jn­di­Util.get­Des­tin­a­tion(UP­DATE_TOP­IC_­NAME);
                                                                                                                                                        up­date­Con­sumer = ses­sion.cre­ate­Con­sumer(up­dat­eTopic);
                                                                                                                                                        up­date­Con­sumer.set­Mes­sageL­istener(this);
                                                                                                                                                
                                                                                                                                                        Queue get­StateQueue = (Queue) Jn­di­Util.get­Des­tin­a­tion(GET_STATE_QUEUE_­NAME);
                                                                                                                                                        get­State­Re­questor = new QueueRe­questor(ses­sion, get­StateQueue);
                                                                                                                                                
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    public void on­Mes­sage(Mes­sage mes­sage) {
                                                                                                                                                        try {
                                                                                                                                                            // mes­sage's con­tents are empty
                                                                                                                                                            up­dateNoState();
                                                                                                                                                        } catch (JM­SEx­cep­tion e) {
                                                                                                                                                            e.print­Stack­Trace();
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    public void attach() throws JM­SEx­cep­tion {
                                                                                                                                                        con­nec­tion.start();
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    public void detach() throws JM­SEx­cep­tion {
                                                                                                                                                        if (con­nec­tion != null) {
                                                                                                                                                            con­nec­tion.stop();
                                                                                                                                                            con­nec­tion.close();
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                
                                                                                                                                                    private void up­dateNoState() throws JM­SEx­cep­tion {
                                                                                                                                                        Text­Mes­sage get­State­Re­quest­Mes­sage = ses­sion.cre­at­e­Text­Mes­sage();
                                                                                                                                                        Mes­sage get­StateReplyMes­sage = get­State­Re­questor.re­quest(get­State­Re­quest­Mes­sage);
                                                                                                                                                        Text­Mes­sage textMsg = (Text­Mes­sage) get­StateReplyMes­sage; // assume cast always works
                                                                                                                                                        String newS­tate = textMsg.get­Text();
                                                                                                                                                        ob­server.update(newS­tate);
                                                                                                                                                    }
                                                                                                                                                }
                                                                                                                                                

                                                                                                                                                同样, PullObserverGateway 与ObserverGateway类似,但多了一些代码来实现拉模型。在初始化时,它不仅设置updateConsumer 来监听更新,还设置 getStateRequestor 来发送GetState ()请求。(getStateRequestor 是QueueRequestor ;请参阅Request - Reply 。)在拉取版本中,网关的onMessage代码忽略消息的内容,因为消息是空的。消息的存在告诉观察者主体已经改变,但它并没有告诉观察者主体的新状态是什么。因此,所需要做的就是调用updateNoState() (名称与notifyNoState()类似)。

                                                                                                                                                Again, Pul­lOb­server­Gate­way is sim­ilar to Ob­server­Gate­way but with some more code to im­ple­ment the pull model. In ini­tial­ize, it sets up not only up­date­Con­sumer to listen for up­dates but also get­State­Re­questor to send Get­State() re­quests. (get­State­Re­questor is a QueueRe­questor; see Re­quest-Reply.) In the pull ver­sion, the gate­way's on­Mes­sage code ig­nores the mes­sage's con­tents be­cause the mes­sage is empty. The mes­sage's ex­ist­ence tells the ob­server that the sub­ject has changed, but it does not tell the ob­server what the sub­ject's new state is. So, all there is to do is call up­dateNoState() (named sim­il­arly to no­ti­fyNoState()).

                                                                                                                                                对于观察者来说,推模型和拉模型之间的差异在updateNoState()update(String)的实现中变得显而易见。推送版本获取新状态作为参数并且只需更新观察者,而拉取版本必须先获取新状态才能更新观察者。为了获取新状态,它使用getStateRequestor发送请求并获得回复。回复包含主题的新状态,网关用它来更新观察者。(请注意,在这个简单的实现中,网关是单线程的,因此在发送获取状态请求并等待回复时,它不会再处理任何更新。因此,如果请求或回复消息确实需要传输时间过长,网关将陷入等待,并且发生的任何更多更新都将简单地排队。)

                                                                                                                                                The dif­fer­ence for the ob­server between the push and pull models be­comes ap­par­ent in the im­ple­ment­a­tion of up­dateNoState() versus update(String). Whereas the push ver­sion gets the new state as a para­meter and just has to update the ob­server, the pull ver­sion must go get the new state before it can update the ob­server. To get the new state, it uses the get­State­Re­questor to send a re­quest and get the reply. The reply con­tains the sub­ject's new state, which the gate­way uses to update the ob­server. (Note that in this simple im­ple­ment­a­tion, the gate­way is single-threaded, so while it is send­ing the get-state re­quest and wait­ing for the reply, it is not pro­cess­ing any more up­dates. Thus, if the re­quest or reply mes­sages take a really long time to trans­mit, the gate­way will be stuck wait­ing, and any more up­dates that occur will simply queue up.)

                                                                                                                                                正如您所看到的,拉模型比推模型更复杂。它需要更多的通道(包括每个观察者的临时通道)和更多的消息(每个感兴趣的观察者每次更新三条消息,而不是所有观察者一条消息),主题和观察者类需要更多代码来管理附加消息传递,而对象在运行时需要更多线程来执行附加消息传递。如果所有这些在您的应用程序中都是可以接受的,那么拉模型就是一种可行的方法。但是,如果有疑问,您可能应该从推送模型开始,因为它更简单。

                                                                                                                                                As you can see, the pull model is more com­plex than the push model. It re­quires more chan­nels (in­clud­ing a tem­por­ary one for every ob­server) and more mes­sages (three mes­sages per update per in­ter­ested ob­server in­stead of one mes­sage for all ob­serv­ers), the sub­ject and ob­server classes re­quire more code to manage the ad­di­tional mes­saging, and the ob­jects at runtime re­quire more threads to ex­ecute the ad­di­tional mes­saging. If all of this is ac­cept­able in your ap­plic­a­tion, then the pull model is a viable ap­proach. How­ever, if in doubt, you should prob­ably start with the push model be­cause it is sim­pler.

                                                                                                                                                渠道设计

                                                                                                                                                Chan­nel Design

                                                                                                                                                到目前为止,我们已经考虑了一个主题,其中一个状态通知其观察者。使用推送模型,这需要一个发布-订阅通道来将主体状态的变化传达给观察者。

                                                                                                                                                So far, we've con­sidered one sub­ject with one piece of state no­ti­fy­ing its ob­serv­ers. Using the push model, this re­quires one Pub­lish-Sub­scribe Chan­nel for com­mu­nic­at­ing changes in the sub­ject's state to the ob­serv­ers.

                                                                                                                                                真正的企业应用程序要复杂得多。一个应用程序可能包含许多需要宣布更改的主题。每个主题通常包含几个不同的状态部分,称为方面,它们可以独立更改。单个观察者可能对几个不同主题的几个不同方面感兴趣,其中主题不仅是同一类的多个实例,而且很可能是不同类的实例。

                                                                                                                                                Real en­ter­prise ap­plic­a­tions are much more com­plex. An ap­plic­a­tion can con­tain lots of sub­jects that need to an­nounce changes. Each sub­ject often con­tains sev­eral dif­fer­ent pieces of state, called as­pects, that can change in­de­pend­ently. A single ob­server might be in­ter­ested in sev­eral dif­fer­ent as­pects in sev­eral dif­fer­ent sub­jects, where the sub­jects are not only mul­tiple in­stances of the same class but may well be in­stances of dif­fer­ent classes.

                                                                                                                                                因此,复杂应用程序中的更新语义很快就会变得复杂。观察者模式将这一问题解决为“观察多个主题”和“明确指定感兴趣的修改”等实现问题。此外,SASE(自地址邮票信封)模式描述了观察者模式命令模式的组合,其中观察者指定当发生某种变化时主体应发送的命令[ Alpert]

                                                                                                                                                So, update se­mantics in soph­ist­ic­ated ap­plic­a­tions can quickly become com­plex. The Ob­server pat­tern ad­dresses this as im­ple­ment­a­tion issues like "Ob­serving more than one sub­ject" and "Spe­cify­ing modi­fic­a­tions of in­terest ex­pli­citly." Also, the SASE (Self-Ad­dresses Stamped En­vel­ope) pat­tern de­scribes a com­bin­a­tion of the Ob­server and Com­mand pat­terns whereby an ob­server spe­cifies the com­mand a sub­ject should send it when a cer­tain change occurs [Alpert].

                                                                                                                                                在不深入讨论确保观察者只收到他们需要的更新的问题的情况下,让我们考虑一下消息传递的影响,即:我们需要多少个渠道?

                                                                                                                                                Without get­ting too deep into the issues of making sure ob­serv­ers re­ceive only the up­dates they need, let's con­sider the im­plic­a­tions for mes­saging, namely: How many chan­nels will we need?

                                                                                                                                                我们首先考虑一个简单的情况。企业可能有多个不同的应用程序负责存储客户的联系信息,例如邮寄地址。当客户的地址在这些应用程序之一中更新时,该应用程序应通知可能也需要此新信息的其他应用程序。同时,可能有多个应用程序需要知道地址何时发生变化,因此它们希望注册以接收通知。

                                                                                                                                                Let's first con­sider a simple case. An en­ter­prise may have sev­eral dif­fer­ent ap­plic­a­tions re­spons­ible for stor­ing a cus­tomer's con­tact in­form­a­tion, such as a mail­ing ad­dress. When a cus­tomer's ad­dress is up­dated in one of these ap­plic­a­tions, the ap­plic­a­tion should notify other ap­plic­a­tions that may need this new in­form­a­tion as well. Mean­while, there may be sev­eral ap­plic­a­tions that need to know when an ad­dress changes, so they would like to re­gister to re­ceive no­ti­fic­a­tion.

                                                                                                                                                这是一个很容易解决的问题。所需要的只是一个用于宣布地址更改的发布-订阅通道。每个可以更改地址的应用程序也有责任通过在通道上发布消息来宣布该更改。每个希望接收通知的应用程序都会订阅该频道。特定的更改消息可能如下所示。

                                                                                                                                                This is a simple prob­lem to solve. All that is needed is a single Pub­lish-Sub­scribe Chan­nel for an­noun­cing ad­dress changes. Each ap­plic­a­tion that can change an ad­dress then also has the re­spons­ib­il­ity to an­nounce that change by pub­lish­ing a mes­sage on the chan­nel. Each ap­plic­a­tion that wishes to re­ceive no­ti­fic­a­tion sub­scribes to the chan­nel. A par­tic­u­lar change mes­sage might look like this.

                                                                                                                                                <地址更改 customer_id="12345">
                                                                                                                                                    <旧地址>
                                                                                                                                                        <街>华尔街123号</街>
                                                                                                                                                        <城市>纽约</城市>
                                                                                                                                                        <州>纽约州</州>
                                                                                                                                                        <邮编>10005</邮编>
                                                                                                                                                    </旧地址>
                                                                                                                                                    <新地址>
                                                                                                                                                        <街道>日落大道321号</街道>
                                                                                                                                                        <城市>洛杉矶</城市>
                                                                                                                                                        <州>加利福尼亚州</州>
                                                                                                                                                        <邮编>90012</邮编>
                                                                                                                                                    </新地址>
                                                                                                                                                </地址更改>
                                                                                                                                                
                                                                                                                                                <Ad­dressChange cus­tom­er­_id="12345">
                                                                                                                                                    <OldAd­dress>
                                                                                                                                                        <Street>123 Wall Street</Street>
                                                                                                                                                        <City>New York</City>
                                                                                                                                                        <State>NY</State>
                                                                                                                                                        <Zip>10005</Zip>
                                                                                                                                                    </OldAd­dress>
                                                                                                                                                    <Ne­wAd­dress>
                                                                                                                                                        <Street>321 Sunset Blvd</Street>
                                                                                                                                                        <City>Los Angeles</City>
                                                                                                                                                        <State>CA</State>
                                                                                                                                                        <Zip>90012</Zip>
                                                                                                                                                    </Ne­wAd­dress>
                                                                                                                                                </Ad­dressChange>
                                                                                                                                                

                                                                                                                                                现在让我们考虑另一个问题。企业还可能有一些应用程序需要在产品缺货时进行通知,而其他应用程序则需要接收这些通知以便重新订购产品。这只是最后一个问题的不同示例,通过使用发布-订阅通道发布产品缺货公告以相同的方式解决。其中一条消息可能如下所示。

                                                                                                                                                Now let's con­sider an­other prob­lem. The en­ter­prise may also have ap­plic­a­tions that need to an­nounce when they are out of a product and others that need to re­ceive these no­ti­fic­a­tions so that can re­order the product. This is just a dif­fer­ent ex­ample of the last prob­lem, and it is solved the same way by using a Pub­lish-Sub­scribe Chan­nel to make out-of-product an­nounce­ments. One of these mes­sages might look like this.

                                                                                                                                                <缺货>
                                                                                                                                                    <产品ID>12345</产品ID>
                                                                                                                                                    <商店ID>67890</商店ID>
                                                                                                                                                    <请求数量>100</请求数量>
                                                                                                                                                </产品外>
                                                                                                                                                
                                                                                                                                                <OutOf­Product>
                                                                                                                                                    <Pro­ductID>12345</Pro­ductID>
                                                                                                                                                    <Stor­eID>67890</Stor­eID>
                                                                                                                                                    <Quant­i­tyRe­ques­ted>100</Quant­i­tyRe­ques­ted>
                                                                                                                                                </OutOf­Product>
                                                                                                                                                

                                                                                                                                                但这让我们想知道:我们可以使用相同的渠道来更改客户地址和发布产品缺货通知吗?可能不会。首先,数据类型通道告诉我们通道上的所有消息必须是相同的类型,在本例中这意味着它们必须都符合相同的 XML 模式。<AddressChange>显然是与<OutOfProduct>非常不同的元素类型,因此它们不应在同一通道上发送。也许可以重新设计数据格式,以便两种消息类型符合相同的模式,然后接收者可以辨别哪些消息是针对地址的,哪些是针对产品的。但问题是,对地址更改感兴趣的应用程序可能与对产品更新感兴趣的应用程序不同,因此,如果消息使用相同的通道,应用程序将经常收到它不感兴趣的通知。因此,有道理两个独立的地址变更和产品更新渠道。

                                                                                                                                                But this leads us to wonder: Can we use the same chan­nel for cus­tomer ad­dress changes and for out-of-product an­nounce­ments? Prob­ably not. First, Data­type Chan­nel tells us that all of the mes­sages on a chan­nel must be the same type, which in this case means that they must all con­form to the same XML schema. <Ad­dressChange> is ob­vi­ously a very dif­fer­ent ele­ment type from <OutOf­Product>, so they should not be sent on the same chan­nel. Per­haps the data formats could be re­worked so that both mes­sage types fit the same schema, and then re­ceiv­ers could tell which mes­sages were for ad­dresses and which were for products. But then the prob­lem is that the ap­plic­a­tions in­ter­ested in ad­dress changes are prob­ably not the same ones in­ter­ested in product up­dates, so if the mes­sages use the same chan­nel, an ap­plic­a­tion will fre­quently re­ceive no­ti­fic­a­tions it's not in­ter­ested in. Thus, it makes sense to have two sep­ar­ate ad­dress change and product update chan­nels.

                                                                                                                                                现在,考虑第三种情况,即客户的信用评级可能发生变化。该消息可能如下所示:

                                                                                                                                                Now, con­sider a third case where a cus­tomer's credit rating could change. The mes­sage might look like this:

                                                                                                                                                <CreditRatingChange customer_id="12345">
                                                                                                                                                    <旧评级>AAA</旧评级>
                                                                                                                                                    <新评级>BBB</新评级>
                                                                                                                                                </信用评级变更>
                                                                                                                                                
                                                                                                                                                <CreditRat­in­gChange cus­tom­er­_id="12345">
                                                                                                                                                    <OldRat­ing>AAA</OldRat­ing>
                                                                                                                                                    <Ne­wRat­ing>BBB</Ne­wRat­ing>
                                                                                                                                                </CreditRat­in­gChange>
                                                                                                                                                

                                                                                                                                                与产品通知的情况一样,使用新的信用评级更改渠道(除了地址更改和产品外渠道之外)可能很容易解决问题。这将使信用评级的变化与地址的变化分开,并且允许家属只注册他们感兴趣的变化类型。

                                                                                                                                                Like the case with product no­ti­fic­a­tions, it might be tempt­ing to solve the prob­lem with a new credit-rating-changed chan­nel (in ad­di­tion to the ad­dress-changed and out-of-product chan­nels). This would keep the credit rating changes sep­ar­ate from the ad­dress changes, and it would allow de­pend­ents to only re­gister for the type of changes they're in­ter­ested in.

                                                                                                                                                这种方法的问题是它可能导致通道爆炸。考虑可能了解的有关客户的所有数据:姓名;用于邮寄、运输和计费的联系人(地址、电话号码、电子邮件);信用评级; 服务水平; 标准折扣;等等。每当这些方面中的任何一个发生变化时,其他应用程序都可能需要了解它。为每个通道创建一个通道可能会产生很多通道。

                                                                                                                                                The prob­lem with this ap­proach is that it can lead to a chan­nel ex­plo­sion. Con­sider all the pieces of data that may be known about a cus­tomer: name; con­tacts (ad­dress, phone number, e-mail) for mail­ing, ship­ping, and billing; credit rating; ser­vice level; stand­ard dis­count; and so on. Each time any one of these as­pects changes, other ap­plic­a­tions may need to know about it. Cre­at­ing a chan­nel for each can lead to lots of chan­nels.

                                                                                                                                                大量的通道可能会给消息系统带来负担。许多通道上的流量很小,这可能会浪费资源并使负载难以分配。包含大量小消息的众多通道可能会增加消息传递开销。家属可能会对要订阅大量频道中的哪一个感到困惑。多个通道需要多个发送者和接收者,可能会导致大量线程检查大量通常为空的通道。因此,创建更多渠道可能不是一个好主意。

                                                                                                                                                Large num­bers of chan­nels may tax the mes­saging system. Nu­mer­ous chan­nels with little traffic on each can waste re­sources and make load dif­fi­cult to dis­trib­ute. Nu­mer­ous chan­nels with lots of little mes­sages can add to mes­saging over­head. De­pend­ents can become con­fused as to which of a large number of chan­nels to sub­scribe to. Mul­tiple chan­nels re­quire mul­tiple senders and re­ceiv­ers, per­haps lead­ing to lots of threads check­ing lots of chan­nels that are usu­ally empty. So, cre­at­ing yet more chan­nels may not be such a good idea.

                                                                                                                                                更好的方法可能是在同一通道上发送地址更改消息和信用评级更改消息,因为它们都涉及对客户的更改,并且对一种更改感兴趣的应用程序也可能对其他更改感兴趣。然而,单独的产品外渠道仍然是一个好主意,因为对客户感兴趣的应用程序可能对产品不感兴趣,反之亦然。

                                                                                                                                                What may work better is to send both the ad­dress-changed and credit-rating-changed mes­sages on the same chan­nel, since they both con­cern changes to the cus­tomer and an ap­plic­a­tion in­ter­ested in one kind of change may be in­ter­ested in the others as well. Yet, a sep­ar­ate out-of-product chan­nel is still a good idea, since ap­plic­a­tions in­ter­ested in cus­tom­ers may not be in­ter­ested in products, and vice versa.

                                                                                                                                                地址更改和信用评级更改的消息具有不同的格式,但数据类型通道告诉我们,要在同一通道上,消息必须具有相同的格式。对于 XML,这意味着所有消息必须具有相同的根元素类型,但可能可以具有不同的可选嵌套元素。因此,统一的客户更改消息可能如下所示:

                                                                                                                                                The ad­dress-changed and credit-rating-changed mes­sages have dif­fer­ent formats, yet Data­type Chan­nel tells us that to be on the same chan­nel, the mes­sages must have the same format. With XML, this means that all of the mes­sages must have the same root ele­ment type but per­haps can have dif­fer­ent op­tional nested ele­ments. So, uni­fied cus­tomer-changed mes­sages might look like this:

                                                                                                                                                <CustomerChange customer_id="12345">
                                                                                                                                                    <地址变更>
                                                                                                                                                        <旧地址>
                                                                                                                                                            <街>华尔街123号</街>
                                                                                                                                                            <城市>纽约</城市>
                                                                                                                                                            <州>纽约州</州>
                                                                                                                                                            <邮编>10005</邮编>
                                                                                                                                                        </旧地址>
                                                                                                                                                        <新地址>
                                                                                                                                                            <街道>日落大道321号</街道>
                                                                                                                                                            <城市>洛杉矶</城市>
                                                                                                                                                            <州>加利福尼亚州</州>
                                                                                                                                                            <邮编>90012</邮编>
                                                                                                                                                        </新地址>
                                                                                                                                                    </地址更改>
                                                                                                                                                </客户变更>
                                                                                                                                                
                                                                                                                                                <CustomerChange customer_id="12345">
                                                                                                                                                    <信用评级变更>
                                                                                                                                                        <旧评级>AAA</旧评级>
                                                                                                                                                        <新评级>BBB</新评级>
                                                                                                                                                    </信用评级变更>
                                                                                                                                                </客户变更>
                                                                                                                                                
                                                                                                                                                <Cus­tom­er­Change cus­tom­er­_id="12345">
                                                                                                                                                    <Ad­dressChange>
                                                                                                                                                        <OldAd­dress>
                                                                                                                                                            <Street>123 Wall Street</Street>
                                                                                                                                                            <City>New York</City>
                                                                                                                                                            <State>NY</State>
                                                                                                                                                            <Zip>10005</Zip>
                                                                                                                                                        </OldAd­dress>
                                                                                                                                                        <Ne­wAd­dress>
                                                                                                                                                            <Street>321 Sunset Blvd</Street>
                                                                                                                                                            <City>Los Angeles</City>
                                                                                                                                                            <State>CA</State>
                                                                                                                                                            <Zip>90012</Zip>
                                                                                                                                                        </Ne­wAd­dress>
                                                                                                                                                    </Ad­dressChange>
                                                                                                                                                </Cus­tom­er­Change>
                                                                                                                                                
                                                                                                                                                <Cus­tom­er­Change cus­tom­er­_id="12345">
                                                                                                                                                    <CreditRat­in­gChange>
                                                                                                                                                        <OldRat­ing>AAA</OldRat­ing>
                                                                                                                                                        <Ne­wRat­ing>BBB</Ne­wRat­ing>
                                                                                                                                                    </CreditRat­in­gChange>
                                                                                                                                                </Cus­tom­er­Change>
                                                                                                                                                

                                                                                                                                                可能仍然存在这样的问题:对地址更改感兴趣的运输应用程序对信用评级更改不感兴趣,而计费应用程序则对相反的情况感兴趣。这些应用程序可以使用选择性消费者来仅获取感兴趣的消息。如果事实证明选择性消费者很复杂,并且消息传递系统可以轻松支持更多渠道,那么也许单独的渠道会更好。

                                                                                                                                                There may still be the prob­lem that ship­ping ap­plic­a­tions in­ter­ested in ad­dress changes are not in­ter­ested in credit rating changes, and billing ap­plic­a­tions are in­ter­ested in the op­pos­ite. These ap­plic­a­tions can use Se­lect­ive Con­sumers to get only the mes­sages of in­terest. If se­lect­ive con­sumers prove to be com­plic­ated and a mes­saging system can easily sup­port more chan­nels, then per­haps sep­ar­ate chan­nels would be better after all.

                                                                                                                                                与企业架构和设计中的许多问题一样,没有简单的答案,需要进行大量的权衡。与任何消息通道一样,发布-订阅通道的目标是帮助确保观察者只收到他们需要的通知,而不会出现单独通道的爆炸性增长,并且不会对典型的观察者造成负担,因为大量线程运行着大量消费者,监控大量数据的频道。

                                                                                                                                                As with many issues in en­ter­prise ar­chi­tec­ture and design, there are no simple an­swers and lots of trade-offs. With Pub­lish-Sub­scribe Chan­nel, as with any mes­sage chan­nel, the goal is to help ensure that the ob­serv­ers re­ceive only the no­ti­fic­a­tions they need, without an ex­plo­sion of sep­ar­ate chan­nels and without taxing the typ­ical ob­server with lots of threads run­ning lots of con­sumers mon­it­or­ing lots of chan­nels.

                                                                                                                                                结论

                                                                                                                                                Con­clu­sions

                                                                                                                                                此示例表明发布-订阅通道观察者模式的一种实现,使得该模式在分布式环境中更易于使用。当使用通道时,Subject.Notify()Observer.Update()变得更加简单,因为他们所要做的就是发送和接收消息。消息系统负责分发和并发,同时使远程通知更加可靠。推送模型比拉取模型更简单且通常更高效,特别是对于分布式通知和消息传递。然而,拉模型也可以使用消息传递来实现。在大量数据可能发生变化的复杂应用程序中,为每个可能变化的不同事物创建一个通道可能很诱人,但使用相同的通道将类似的通知传输给相同的观察者通常更实际。即使您的应用程序不需要其他任何消息传递,如果它们需要相互通知更改,那么它也可能值得使用消息传递只是为了让您可以利用发布-订阅通道

                                                                                                                                                This ex­ample shows that Pub­lish-Sub­scribe Chan­nels are an im­ple­ment­a­tion of the Ob­server pat­tern that makes the pat­tern much easier to use in dis­trib­uted en­vir­on­ments. When a chan­nel is used, Sub­ject.Notify() and Ob­server.Update() become much sim­pler be­cause all they have to do is send and re­ceive mes­sages. The mes­saging system takes care of dis­tri­bu­tion and con­cur­rency while making the remote no­ti­fic­a­tion more re­li­able. The push model is sim­pler and often more ef­fi­cient than the pull model, es­pe­cially for dis­trib­uted no­ti­fic­a­tion and with mes­saging. Yet, the pull model can also be im­ple­men­ted using mes­saging. In com­plex ap­plic­a­tions where lots of data can change, it may be tempt­ing to create a chan­nel for every dif­fer­ent thing that can change, but it's often more prac­tical to use the same chan­nel to trans­mit sim­ilar no­ti­fic­a­tions going to the same ob­serv­ers. Even if your ap­plic­a­tions don't need mes­saging for any­thing else, if they need to notify each other of changes, it may well be worth using Mes­saging just so you can take ad­vant­age of Pub­lish-Sub­scribe Chan­nels.

                                                                                                                                                  介绍

                                                                                                                                                  Introduction

                                                                                                                                                  第 3 章“消息系统”中,我们讨论了如何使用消息路由器将消息源与消息的最终目的地解耦。本章详细介绍了特定类型的消息路由器,以解释如何为集成解决方案提供路由和代理功能。大多数模式都是消息路由器模式的改进,而其他模式则结合多个消息路由器来解决更复杂的问题。因此,我们可以将消息路由模式分为以下几组:

                                                                                                                                                  In Chapter 3, "Mes­saging Sys­tems," we dis­cussed how a Mes­sage Router can be used to de­couple a mes­sage source from the ul­ti­mate des­tin­a­tion of the mes­sage. This chapter elab­or­ates on spe­cific types of Mes­sage Routers to ex­plain how to provide rout­ing and broker­ing abil­ity to an in­teg­ra­tion solu­tion. Most pat­terns are re­fine­ments of the Mes­sage Router pat­tern, while others com­bine mul­tiple Mes­sage Routers to solve more com­plex prob­lems. There­fore, we can cat­egor­ize the mes­sage rout­ing pat­terns into the fol­low­ing groups:

                                                                                                                                                  • 简单路由器消息路由器的变体,它将消息从一个入站通道路由到一个或多个出站通道。

                                                                                                                                                  • Simple Routers are vari­ants of the Mes­sage Router and route mes­sages from one in­bound chan­nel to one or more out­bound chan­nels.

                                                                                                                                                  • 组合路由器组合多个简单路由器来创建更复杂的消息流。

                                                                                                                                                  • Com­posed Routers com­bine mul­tiple simple routers to create more com­plex mes­sage flows.

                                                                                                                                                  • 架构模式描述了基于消息路由器的架构风格

                                                                                                                                                  • Ar­chi­tec­tural Pat­terns de­scribe ar­chi­tec­tural styles based on Mes­sage Routers.

                                                                                                                                                  简单路由器

                                                                                                                                                  Simple Routers

                                                                                                                                                  基于内容的路由器检查消息的内容并根据消息的内容将其路由到另一个通道。使用这样的路由器使消息生产者能够将消息发送到单个通道,并将其留给基于内容的路由器将它们路由到正确的目的地。这减轻了发送应用程序的这项任务,并避免将消息生产者耦合到特定的目标通道。

                                                                                                                                                  The Con­tent-Based Router in­spects the con­tent of a mes­sage and routes it to an­other chan­nel based on the con­tent of the mes­sage. Using such a router en­ables the mes­sage pro­du­cer to send mes­sages to a single chan­nel and leave it to the Con­tent-Based Router to route them to the proper des­tin­a­tion. This al­le­vi­ates the send­ing ap­plic­a­tion from this task and avoids coup­ling the mes­sage pro­du­cer to spe­cific des­tin­a­tion chan­nels.

                                                                                                                                                  消息过滤器基于内容的路由器的一种特殊形式。它检查消息内容,仅当消息内容符合特定条件时才将消息传递到另一个通道。否则,它会丢弃该消息。消息过滤器执行的功能与选择性消费者的功能非常相似,主要区别在于消息过滤器是消息传递系统的一部分,将合格的消息路由到另一个通道,而选择性消费者内置于消息端点中端点中。

                                                                                                                                                  A Mes­sage Filter is a spe­cial form of a Con­tent-Based Router. It ex­am­ines the mes­sage con­tent and passes the mes­sage to an­other chan­nel only if the mes­sage con­tent matches cer­tain cri­teria. Oth­er­wise, it dis­cards the mes­sage. A Mes­sage Filter per­forms a func­tion that is very sim­ilar to that of a Se­lect­ive Con­sumer with the key dif­fer­ence that a Mes­sage Filter is part of the mes­saging system, rout­ing qual­i­fy­ing mes­sages to an­other chan­nel, whereas a Se­lect­ive Con­sumer is built into a Mes­sage En­d­point.

                                                                                                                                                  基于内容的路由器和消息过滤器实际上可以解决类似的问题。基于内容的路由器根据基于内容的路由器中编码的标准将消息路由到正确的目的地。通过使用发布-订阅通道和一组消息过滤器(每个潜在接收者一个)可以实现等效行为。每个消息过滤器都会消除与特定目标的条件不匹配的消息。基于内容的路由器预测性地路由到单个通道,因此具有完全控制权,但它也依赖于所有可能的目标通道的列表。相比之下,消息过滤器阵列进行反应性过滤,将路由逻辑分布在许多消息过滤器上,但避免依赖于所有可能目的地的单个组件。消息过滤器模式中更详细地描述了这些解决方案之间的权衡。

                                                                                                                                                  A Con­tent-Based Router and a Mes­sage Filter can ac­tu­ally solve a sim­ilar prob­lem. A Con­tent-Based Router routes a mes­sage to the cor­rect des­tin­a­tion based on the cri­teria en­coded in the Con­tent-Based Router. Equi­val­ent be­ha­vior can be achieved by using a Pub­lish-Sub­scribe Chan­nel and an array of Mes­sage Fil­ters, one for each po­ten­tial re­cip­i­ent. Each Mes­sage Filter elim­in­ates the mes­sages that do not match the cri­teria for the spe­cific des­tin­a­tion. The Con­tent-Based Router routes pre­dict­ively to a single chan­nel and there­fore has total con­trol, but it is also de­pend­ent on the list of all pos­sible des­tin­a­tion chan­nels. In con­trast, the Mes­sage Filter array fil­ters re­act­ively, spread­ing the rout­ing logic across many Mes­sage Fil­ters but avoid­ing a single com­pon­ent that is de­pend­ent on all pos­sible des­tin­a­tions. The trade-off between these solu­tions is de­scribed in more detail in the Mes­sage Filter pat­tern.

                                                                                                                                                  基本消息路由器使用固定规则来确定传入消息的目的地。当我们需要更大的灵活性时,动态路由器会非常有用。该路由器允许通过将控制消息发送到指定的控制端口来修改路由逻辑。动态路由器的动态特性可以与大多数形式的消息路由器相结合。

                                                                                                                                                  A basic Mes­sage Router uses fixed rules to de­term­ine the des­tin­a­tion of an in­com­ing mes­sage. Where we need more flex­ib­il­ity, a Dy­namic Router can be very useful. This router allows the rout­ing logic to be mod­i­fied by send­ing con­trol mes­sages to a des­ig­nated con­trol port. The dy­namic nature of the Dy­namic Router can be com­bined with most forms of the Mes­sage Router.

                                                                                                                                                  第 4 章“消息传递通道”介绍了点对点通道发布-订阅通道的概念。有时,您需要向多个收件人发送一封邮件,但希望保持对收件人的控制。收件人列表可以让您做到这一点。本质上,收件人列表是一个基于内容的路由器,可以将单个消息路由到多个目标通道。

                                                                                                                                                  Chapter 4, "Mes­saging Chan­nels," in­tro­duced the con­cepts of Point-to-Point Chan­nel and Pub­lish-Sub­scribe Chan­nel. Some­times, you need to send a mes­sage to more than one re­cip­i­ent but want to main­tain con­trol over the re­cip­i­ents. The Re­cip­i­ent List allows you do just that. In es­sence, a Re­cip­i­ent List is a Con­tent-Based Router that can route a single mes­sage to more than one des­tin­a­tion chan­nel.

                                                                                                                                                  有些消息包含单个项目的列表。您如何单独处理这些项目?使用分离器大消息拆分为单独的消息。然后,每条消息都可以进一步路由并单独处理。

                                                                                                                                                  Some mes­sages con­tain lists of in­di­vidual items. How do you pro­cess these items in­di­vidu­ally? Use a Split­ter to split the large mes­sage into in­di­vidual mes­sages. Each mes­sage can then be routed fur­ther and pro­cessed in­di­vidu­ally.

                                                                                                                                                  但是,您可能需要将拆分器创建的消息重新组合回单个消息。这是聚合器执行的功能之一。聚合器可以接收消息流、识别相关消息并将它们组合成单个消息。与其他路由模式不同,聚合器是一个有状态的消息路由器,因为它必须在内部存储消息,直到满足特定条件。这意味着聚合器在发布消息之前可以使用多条消息。

                                                                                                                                                  How­ever, you may need to re­com­bine the mes­sages that the Split­ter cre­ated back into a single mes­sage. This is one of the func­tions an Ag­greg­ator per­forms. An Ag­greg­ator can re­ceive a stream of mes­sages, identify re­lated mes­sages, and com­bine them into a single mes­sage. Unlike the other rout­ing pat­terns, the Ag­greg­ator is a state­ful Mes­sage Router be­cause it has to store mes­sages in­tern­ally until spe­cific con­di­tions are ful­filled. This means that an Ag­greg­ator can con­sume mul­tiple mes­sages before it pub­lishes a mes­sage.

                                                                                                                                                  因为我们使用消息传递来连接在多台计算机上运行的应用程序或组件,所以可以并行处理多个消息。例如,多个进程可能会消耗来自单个通道的消息。其中一个进程可能比另一个进程执行得更快,从而导致消息处理无序。然而,某些组件(例如基于分类帐的系统)依赖于各个消息的正确顺序。重排序器将失序的消息重新按顺序排列。排序器也是一个有状态的消息路由器,因为它可能需要在内部存储大量消息,直到完成序列的消息到达。与聚合器不同不过,重排序器最终会发布与其消耗的相同数量的消息。

                                                                                                                                                  Be­cause we use mes­saging to con­nect ap­plic­a­tions or com­pon­ents run­ning on mul­tiple com­puters, mul­tiple mes­sages can be pro­cessed in par­al­lel. For ex­ample, more than one pro­cess may con­sume mes­sages off a single chan­nel. One of these pro­cesses may ex­ecute faster than an­other, caus­ing mes­sages to be pro­cessed out of order. How­ever, some com­pon­ents­for ex­ample, ledger-based sys­tems­de­pend on the cor­rect se­quence of in­di­vidual mes­sages. The Resequen­cer puts out-of-se­quence mes­sages back into se­quence. The Resequen­cer is also a state­ful Mes­sage Router be­cause it may need to store a number of mes­sages in­tern­ally until the mes­sage that com­pletes the se­quence ar­rives. Unlike the Ag­greg­ator, though, the Resequen­cer ul­ti­mately pub­lishes the same number of mes­sages it con­sumed.

                                                                                                                                                  下表总结了消息路由器变体的属性(我们没有将动态路由器作为单独的替代方案包括在内,因为任何路由器都可以实现为动态变体):

                                                                                                                                                  The fol­low­ing table sum­mar­izes the prop­er­ties of the Mes­sage Router vari­ants (we did not in­clude the Dy­namic Router as a sep­ar­ate al­tern­at­ive be­cause any router can be im­ple­men­ted as a dy­namic vari­ant):

                                                                                                                                                  图案

                                                                                                                                                  Pat­tern

                                                                                                                                                  消费消息数

                                                                                                                                                  Number of Mes­sages Con­sumed

                                                                                                                                                  发布消息数

                                                                                                                                                  Number of Mes­sages Pub­lished

                                                                                                                                                  有状态?

                                                                                                                                                  State­ful?

                                                                                                                                                  评论

                                                                                                                                                  Com­ment

                                                                                                                                                  基于内容的路由器

                                                                                                                                                  Con­tent-Based Router

                                                                                                                                                  1

                                                                                                                                                  1

                                                                                                                                                  1

                                                                                                                                                  1

                                                                                                                                                  没有(大部分)

                                                                                                                                                  No (mostly)

                                                                                                                                                   

                                                                                                                                                  筛选

                                                                                                                                                  Filter

                                                                                                                                                  1

                                                                                                                                                  1

                                                                                                                                                  0 或 1

                                                                                                                                                  0 or 1

                                                                                                                                                  没有(大部分)

                                                                                                                                                  No (mostly)

                                                                                                                                                   

                                                                                                                                                  收件人名单

                                                                                                                                                  Re­cip­i­ent List

                                                                                                                                                  1

                                                                                                                                                  1

                                                                                                                                                  多个(包括0)

                                                                                                                                                  mul­tiple (incl. 0)

                                                                                                                                                  No

                                                                                                                                                   

                                                                                                                                                  音序器

                                                                                                                                                  Se­quen­cer

                                                                                                                                                  1

                                                                                                                                                  1

                                                                                                                                                  多种的

                                                                                                                                                  mul­tiple

                                                                                                                                                  No

                                                                                                                                                   

                                                                                                                                                  聚合器

                                                                                                                                                  Ag­greg­ator

                                                                                                                                                  多种的

                                                                                                                                                  mul­tiple

                                                                                                                                                  1

                                                                                                                                                  1

                                                                                                                                                  是的

                                                                                                                                                  Yes

                                                                                                                                                   

                                                                                                                                                  重排序器

                                                                                                                                                  Resequen­cer

                                                                                                                                                  多种的

                                                                                                                                                  mul­tiple

                                                                                                                                                  多种的

                                                                                                                                                  mul­tiple

                                                                                                                                                  是的

                                                                                                                                                  Yes

                                                                                                                                                  发布与消耗的数量相同的数量

                                                                                                                                                  Pub­lishes same number it con­sumes

                                                                                                                                                  组合路由器

                                                                                                                                                  Com­posed Routers

                                                                                                                                                  管道和过滤器架构的一个关键优势是我们可以将多个过滤器组合成一个更大的解决方案。组合消息处理器分散收集结合了多个消息路由器变体以创建更全面的解决方案。这两种模式都允许我们从多个来源检索信息并将其重新组合成单​​个消息。组合消息处理器将单个消息拆分为多个部分,而分散-聚集将同一消息的副本发送给多个收件人。

                                                                                                                                                  A key ad­vant­age of the Pipes and Fil­ters ar­chi­tec­ture is that we can com­pose mul­tiple fil­ters into a larger solu­tion. Com­posed Mes­sage Pro­cessor and Scat­ter-Gather com­bine mul­tiple Mes­sage Router vari­ants to create more com­pre­hens­ive solu­tions. Both pat­terns allow us to re­trieve in­form­a­tion from mul­tiple sources and re­com­bine it into a single mes­sage. The Com­posed Mes­sage Pro­cessor splits a single mes­sage into mul­tiple parts, whereas the Scat­ter-Gather send a copy of the same mes­sage to mul­tiple re­cip­i­ents.

                                                                                                                                                  组合消息处理器分散-聚集都将一条消息同时路由到多个参与者,并将回复重新组合成一条消息。我们可以说这些模式管理消息的并行路由。另外两个模式管理消息的顺序路由,即通过一系列单独的步骤来路由消息。如果我们想从一个中心点控制消息的路径,我们可以使用路由单指定消息应采用的路径。此模式的工作原理就像附加到办公文档的路由表一样,由多个收件人按顺序传递它们。或者,我们可以使用Process Manager ,它为我们提供了更大的灵活性,但要求消息在每个函数之后返回到中央组件。

                                                                                                                                                  Both the Com­posed Mes­sage Pro­cessor and the Scat­ter-Gather route a single mes­sage to a number of par­ti­cipants con­cur­rently and re­as­semble the replies into a single mes­sage. We can say that these pat­terns manage the par­al­lel rout­ing of a mes­sage. Two ad­di­tional pat­terns manage the se­quen­tial rout­ing of a mes­sage, that is, rout­ing a mes­sage through a se­quence of in­di­vidual steps. If we want to con­trol the path of a mes­sage from a cent­ral point, we can use a Rout­ing Slip to spe­cify the path the mes­sage should take. This pat­tern works just like the rout­ing slip at­tached to office doc­u­ments to pass them se­quen­tially by a number of re­cip­i­ents. Al­tern­at­ively, we can use a Pro­cess Man­ager, which gives us more flex­ib­il­ity but re­quires the mes­sage to return to a cent­ral com­pon­ent after each func­tion.

                                                                                                                                                  建筑模式

                                                                                                                                                  Ar­chi­tec­tural Pat­terns

                                                                                                                                                  消息路由器使我们能够使用中央消息代理构建集成解决方案。与不同的消息路由设计模式相反,该模式描述了中心辐射型架构风格。

                                                                                                                                                  Mes­sage Routers enable us to ar­chi­tect an in­teg­ra­tion solu­tion using a cent­ral Mes­sage Broker. As op­posed to the dif­fer­ent mes­sage rout­ing design pat­terns, this pat­tern de­scribes a hub-and-spoke ar­chi­tec­tural style.

                                                                                                                                                  适合正确用途的正确路由器

                                                                                                                                                  The Right Router for the Right Pur­pose

                                                                                                                                                  本章包含 12 个模式。我们如何才能轻松找到适合正确目的的正确模式?以下决策图可帮助您通过简单的是或否决策找到用于正确目的的正确模式。例如,如果您正在寻找一种简单的路由模式,一次使用一条消息但按顺序发布多条消息,则应该使用 Splitter 。 该图还有助于说明各个模式的相关程度。例如,路由表流程管理器解决类似的问题,而消息过滤器则做一些完全不同的事情。

                                                                                                                                                  This chapter con­tains 12 pat­terns. How can we make it easy to find the right pat­tern for the right pur­pose? The fol­low­ing de­cision chart helps you find the right pat­tern for the right pur­pose through simple yes or no de­cisions. For ex­ample, if you are look­ing for a simple rout­ing pat­tern that con­sumes one mes­sage at a time but pub­lishes mul­tiple mes­sages in se­quen­tial order, you should use a Split­ter. The dia­gram also helps il­lus­trate how closely the in­di­vidual pat­terns are re­lated. For ex­ample, a Rout­ing Slip and a Pro­cess Man­ager solve sim­ilar prob­lems, while a Mes­sage Filter does some­thing rather dif­fer­ent.

                                                                                                                                                  图形/07inf01.gif

                                                                                                                                                    基于内容的路由器

                                                                                                                                                    Content-Based Router

                                                                                                                                                    图形/contentbasedrouter_icon.gif

                                                                                                                                                    假设我们正在构建一个订单处理系统。收到传入订单后,我们首先验证订单,然后验证订购的商品在仓库中是否可用。该功能由库存系统执行。这一系列处理步骤是管道和过滤器风格的完美候选者。我们创建两个过滤器,一个用于验证步骤,一个用于库存系统,并通过两个过滤器路由传入消息。然而,在许多企业集成场景中,存在多个库存系统,并且每个系统只能处理特定的物料。

                                                                                                                                                    Assume that we are build­ing an order-pro­cess­ing system. When an in­com­ing order is re­ceived, we first val­id­ate the order and then verify that the ordered item is avail­able in the ware­house. This func­tion is per­formed by the in­vent­ory system. This se­quence of pro­cess­ing steps is a per­fect can­did­ate for the Pipes and Fil­ters style. We create two fil­ters, one for the val­id­a­tion step and one for the in­vent­ory system, and route the in­com­ing mes­sages through both fil­ters. How­ever, in many en­ter­prise in­teg­ra­tion scen­arios more than one in­vent­ory system exists, and each system can handle only spe­cific items.

                                                                                                                                                    我们如何处理单个逻辑功能的实现分布在多个物理系统上的情况?

                                                                                                                                                    How do we handle a situ­ation in which the im­ple­ment­a­tion of a single lo­gical func­tion is spread across mul­tiple phys­ical sys­tems?



                                                                                                                                                    集成解决方案连接现有应用程序,以便它们协同工作。由于许多此类应用程序在开发时并未考虑到集成,因此集成解决方案很少能找到将业务功能很好地封装在单个系统内的理想场景。例如,收购或业务合作伙伴关系通常会导致多个系统执行相同的业务功能。此外,许多充当聚合商或经销商的企业通常与执行相同功能(例如,检查库存、下订单)的多个系统进行交互。更复杂的是,这些系统可能在公司内部运行,也可能处于业务合作伙伴或附属公司的控制之下。例如,像亚马逊这样的大型电子零售商允许您订购从书籍到电锯到服装的任何商品。根据商品的类型,订单可以由不同的“幕后”商家的订单处理系统来处理。

                                                                                                                                                    In­teg­ra­tion solu­tions con­nect ex­ist­ing ap­plic­a­tions so that they work to­gether. Be­cause many of these ap­plic­a­tions were de­ve­loped without in­teg­ra­tion in mind, in­teg­ra­tion solu­tions rarely find an ideal scen­ario where a busi­ness func­tion is well en­cap­su­lated inside a single system. For ex­ample, ac­quis­i­tions or busi­ness part­ner­ships often result in mul­tiple sys­tems per­form­ing the same busi­ness func­tion. Also, many busi­nesses that act as ag­greg­at­ors or re­sellers typ­ic­ally in­ter­face with mul­tiple sys­tems that per­form the same func­tions (e.g., check in­vent­ory, place order). To make mat­ters more com­plic­ated, these sys­tems may be op­er­ated within the com­pany or may be under the con­trol of busi­ness part­ners or af­fil­i­ates. For ex­ample, large e-tail­ers like Amazon allow you to order any­thing from books to chain­saws to cloth­ing. De­pend­ing on the type of item, the order may be pro­cessed by a dif­fer­ent "behind-the-scenes" mer­chant's order pro­cess­ing sys­tems.

                                                                                                                                                    假设该公司正在销售小部件和小工具,并且有两个库存系统:一个用于小部件,另一个用于小工具。我们还假设每个项目都由唯一的项目编号标识。当公司收到订单时,需要根据订购的商品类型来决定由哪个库存系统接收订单。我们可以根据订购的商品类型为传入订单创建单独的渠道。然而,这需要客户了解我们的内部系统架构,而实际上他们可能甚至不知道我们区分小部件和小工具。因此,我们应该向集成解决方案的其余部分(包括客户)隐藏业务功能的实现分布在多个系统中的事实。所以,

                                                                                                                                                    Let's assume that the com­pany is selling wid­gets and gad­gets and has two in­vent­ory sys­tems: one for wid­gets and one for gad­gets. Let's also assume that each item is iden­ti­fied by a unique item number. When the com­pany re­ceives an order, it needs to decide which in­vent­ory system should re­ceive the order based on the type of item ordered. We could create sep­ar­ate chan­nels for in­com­ing orders based on the type of item ordered. How­ever, this would re­quire the cus­tom­ers to know our in­ternal system ar­chi­tec­ture when in fact they may not even be aware that we dis­tin­guish between wid­gets and gad­gets. There­fore, we should hide the fact that the im­ple­ment­a­tion of the busi­ness func­tion is spread across mul­tiple sys­tems from the re­mainder of the in­teg­ra­tion solu­tion, in­clud­ing cus­tom­ers. There­fore, we must expect mes­sages for dif­fer­ent items to arrive on the same chan­nel.

                                                                                                                                                    我们可以将订单转发到所有库存系统(使用发布-订阅通道),并让每个系统决定是否可以处理该订单。这种方法使得添加新的库存系统变得容易,因为当新的库存系统上线时,我们不必更改任何现有组件。然而,这种方法假设跨多个系统进行分布式协调。如果任何系统都无法处理订单,会发生什么情况?或者是否有多个系统可以处理该订单?客户会收到重复的货件吗?此外,在许多情况下,库存系统会将无法处理的商品订单视为错误。如果是这种情况,则每个订单都会导致除一个库存系统之外的所有库存系统出现错误。很难将这些错误与“真实”错误(例如无效订单)区分开来。

                                                                                                                                                    We could for­ward the order to all in­vent­ory sys­tems (using a Pub­lish-Sub­scribe Chan­nel ), and let each system decide whether it can handle the order. This ap­proach makes the ad­di­tion of new in­vent­ory sys­tems easy be­cause we do not have to change any of the ex­ist­ing com­pon­ents when a new in­vent­ory system comes online. How­ever, this ap­proach as­sumes dis­trib­uted co­ordin­a­tion across mul­tiple sys­tems. What hap­pens if the order cannot be pro­cessed by any system? Or if more than one system can pro­cess the order? Will the cus­tomer re­ceive du­plic­ate ship­ments? Also, in many cases an in­vent­ory system will treat an order for an item that it cannot handle as an error. If this is the case, each order would cause errors in all in­vent­ory sys­tems but one. It would be hard to dis­tin­guish these errors from "real" errors, such as an in­valid order.

                                                                                                                                                    另一种方法是使用商品编号作为频道地址。每个商品都有其专用的渠道,客户可以简单地将订单发布到与商品编号关联的渠道,而无需了解小部件和小工具之间的任何内部区别。库存系统可以在所有渠道上侦听它可以处理的那些物品。此方法利用通道可寻址性将消息路由到正确的库存系统。然而,大量的项目可能很快导致通道数量激增,给系统带来运行时和管理开销的负担。为每个提供的商品创建新渠道很快就会导致混乱。

                                                                                                                                                    An al­tern­at­ive ap­proach would be to use the item number as a chan­nel ad­dress. Each item would have its ded­ic­ated chan­nel, and the cus­tom­ers could simply pub­lish the order to the chan­nel as­so­ci­ated with the item's number without having to know about any in­ternal dis­tinc­tions between wid­gets and gad­gets. The in­vent­ory sys­tems could listen on all the chan­nels for those items that it can pro­cess. This ap­proach lever­ages the chan­nel ad­dress­ab­il­ity to route mes­sages to the cor­rect in­vent­ory system. How­ever, a large number of items could quickly lead to an ex­plo­sion of the number of chan­nels, bur­den­ing the system with runtime and man­age­ment over­head. Cre­at­ing new chan­nels for each item that is offered would quickly result in chaos.

                                                                                                                                                    我们还应该尽量减少消息流量。例如,我们可以将订单消息依次通过一个库存系统。第一个可以接受订单的系统消费该消息并处理该订单。如果它无法处理订单,则会将订单消息传递到下一个系统。这种方法消除了订单同时被多个系统接受的危险。此外,我们知道如果最后一个系统将订单传回,则该订单未被任何系统处理。然而,该解决方案确实要求系统彼此足够了解,以便将消息从一个系统传递到下一个系统。这种方法类似于责任链[ GoF]。然而,在基于消息的集成领域,通过一系列系统传递消息可能意味着巨大的开销。此外,这种方法需要各个系统之间的协作,如果某些系统由外部业务合作伙伴维护,因此不受我们的控制,则这可能不可行。

                                                                                                                                                    We should also try to min­im­ize mes­sage traffic. For ex­ample, we could route the order mes­sage through one in­vent­ory system after the other. The first system that can accept the order con­sumes the mes­sage and pro­cesses the order. If it cannot pro­cess the order, it passes the order mes­sage to the next system. This ap­proach elim­in­ates the danger of orders being ac­cep­ted by mul­tiple sys­tems sim­ul­tan­eously. Also, we know that the order was not pro­cessed by any system if the last system passes it back. The solu­tion does re­quire, how­ever, that the sys­tems know enough about each other to pass the mes­sage from one system to the next. This ap­proach is sim­ilar to the Chain of Re­spons­ib­il­ity pat­tern [GoF]. How­ever, in the world of mes­sage-based in­teg­ra­tion, passing mes­sages through a chain of sys­tems could mean sig­ni­fic­ant over­head. Also, this ap­proach would re­quire col­lab­or­a­tion of the in­di­vidual sys­tems, which may not be feas­ible if some sys­tems are main­tained by ex­ternal busi­ness part­ners and are there­fore not under our con­trol.

                                                                                                                                                    总之,我们需要一种解决方案,它能够封装业务功能跨系统拆分的事实,高效地使用消息通道和消息流量,并确保订单仅由一个库存系统处理。

                                                                                                                                                    In sum­mary, we need a solu­tion that en­cap­su­lates the fact that the busi­ness func­tion is split across sys­tems, is ef­fi­cient in its use of mes­sage chan­nels and mes­sage traffic, and en­sures that the order is handled by ex­actly one in­vent­ory system.

                                                                                                                                                    使用基于内容的路由器根据消息的内容将每条消息路由到正确的收件人。

                                                                                                                                                    Use a Con­tent-Based Router to route each mes­sage to the cor­rect re­cip­i­ent based on the mes­sage's con­tent.

                                                                                                                                                    图形/07inf02.gif



                                                                                                                                                    基于内容的路由器检查消息内容并根据消息中包含的数据将消息路由到不同的通道。路由可以基于许多标准,例如字段的存在、特定字段值等等。在实现基于内容的路由器时,应特别注意使路由功能易于维护,因为路由器可能成为频繁维护的点。在更复杂的集成场景中,基于内容的路由器可以采用可配置规则引擎的形式,该引擎根据一组可配置规则计算目标通道。

                                                                                                                                                    The Con­tent-Based Router ex­am­ines the mes­sage con­tent and routes the mes­sage onto a dif­fer­ent chan­nel based on data con­tained in the mes­sage. The rout­ing can be based on a number of cri­teria, such as ex­ist­ence of fields, spe­cific field values, and so on. When im­ple­ment­ing a Con­tent-Based Router, spe­cial cau­tion should be taken to make the rout­ing func­tion easy to main­tain, as the router can become a point of fre­quent main­ten­ance. In more soph­ist­ic­ated in­teg­ra­tion scen­arios, the Con­tent-Based Router can take on the form of a con­fig­ur­able rules engine that com­putes the des­tin­a­tion chan­nel based on a set of con­fig­ur­able rules.

                                                                                                                                                    减少依赖

                                                                                                                                                    Re­du­cing De­pend­en­cies

                                                                                                                                                    基于内容的路由器是更通用的消息路由器的一种常用形式。它使用预测路由,也就是说,它结合了所有其他系统的功能知识。这有助于高效路由,因为每个传出消息都直接发送到正确的系统。缺点是基于内容的路由器必须了解所有可能的接收者及其能力。当添加、删除或更改收件人时,每次都必须更改基于内容的路由器。这可能会成为维护的噩梦。

                                                                                                                                                    Con­tent-Based Router is a fre­quently used form of the more gen­eric Mes­sage Router. It uses pre­dict­ive rout­ingthat is, it in­cor­por­ates know­ledge of the cap­ab­il­it­ies of all other sys­tems. This makes for ef­fi­cient rout­ing be­cause each out­go­ing mes­sage is sent dir­ectly to the cor­rect system. The down­side is that the Con­tent-Based Router has to have know­ledge of all pos­sible re­cip­i­ents and their cap­ab­il­it­ies. As re­cip­i­ents are added, re­moved, or changed, the Con­tent-Based Router has to be changed every time. This can become a main­ten­ance night­mare.

                                                                                                                                                    如果收件人对路由过程有更多的控制,我们就可以避免基于内容的路由器对各个收件人的依赖。这些选项可以概括为反应式过滤,因为它们允许每个参与者过滤相关消息。路由控制的分布消除了对基于内容的路由器的需要,但该解决方案通常效率较低。这些解决方案和相关的权衡在消息过滤器路由表中有更详细的描述。

                                                                                                                                                    We can avoid the de­pend­ency of the Con­tent-Based Router on the in­di­vidual re­cip­i­ents if the re­cip­i­ents assume more con­trol over the rout­ing pro­cess. These op­tions can be sum­mar­ized as re­act­ive fil­ter­ing be­cause they allow each par­ti­cipant to filter rel­ev­ant mes­sages as they come by. The dis­tri­bu­tion of rout­ing con­trol elim­in­ates the need for a Con­tent-Based Router, but the solu­tion is gen­er­ally less ef­fi­cient. These solu­tions and as­so­ci­ated trade-offs are de­scribed in more detail in the Mes­sage Filter and Rout­ing Slip.

                                                                                                                                                    动态路由器通过让每个接收者向基于内容的路由器告知其功能,描述了基于内容的路由器和反应式过滤方法之间的折衷。基于内容的路由器维护每个接收者的能力的列表并相应地路由传入的消息。与简单的基于内容的路由器相比,我们为这种灵活性付出的代价是解决方案的复杂性以及调试此类系统的难度。

                                                                                                                                                    The Dy­namic Router de­scribes a com­prom­ise between the Con­tent-Based Router and the re­act­ive fil­ter­ing ap­proach by having each re­cip­i­ent inform the Con­tent-Based Router of its cap­ab­il­it­ies. The Con­tent-Based Router main­tains a list of each re­cip­i­ent's cap­ab­il­it­ies and routes in­com­ing mes­sages ac­cord­ingly. The price we pay for this flex­ib­il­ity is the com­plex­ity of the solu­tion and the dif­fi­culty of de­bug­ging such a system when com­pared to a simple Con­tent-Based Router.

                                                                                                                                                    示例: 使用 C# 和 MSMQ 的基于内容的路由器

                                                                                                                                                    Ex­ample: Con­tent-Based Router with C# and MSMQ

                                                                                                                                                    此代码示例演示了一个非常简单的基于内容的路由器,它根据消息正文中的第一个字符来路由消息。如果正文以 W 开头,则路由器将消息路由到 widgetQueue ; 如果以 G 开头,则进入 gadgetQueue 。 如果两者都不是,则路由器将其发送到dunnoQueue 。该队列实际上是无效消息通道的一个示例。该路由器是无状态的,也就是说,它在做出路由决策时不会“记住”任何先前的消息。

                                                                                                                                                    This code ex­ample demon­strates a very simple Con­tent-Based Router that routes mes­sages based on the first char­ac­ter in the mes­sage body. If the body text starts with W, the router routes the mes­sage to the wid­getQueue; if it starts with G, it goes to the gad­getQueue. If it is neither, the router sends it to the dun­noQueue. This queue is ac­tu­ally an ex­ample of an In­valid Mes­sage Chan­nel. This router is state­less, that is, it does not "re­mem­ber" any pre­vi­ous mes­sages when making the rout­ing de­cision.

                                                                                                                                                    
                                                                                                                                                    类 ContentBaseRouter
                                                                                                                                                    {
                                                                                                                                                        受保护的消息队列inQueue;
                                                                                                                                                        受保护的消息队列widgetQueue;
                                                                                                                                                        受保护的消息队列gadgetQueue;
                                                                                                                                                        受保护的消息队列不知道队列;
                                                                                                                                                    
                                                                                                                                                        公共ContentBasedRouter(消息队列inQueue,消息队列
                                                                                                                                                    图形/ccc.gif小部件队列,
                                                                                                                                                                                  消息队列gadgetQueue,
                                                                                                                                                    图形/ccc.gif消息队列(不知道队列)
                                                                                                                                                        {
                                                                                                                                                            this.inQueue = inQueue;
                                                                                                                                                            this.widgetQueue = widgetQueue;
                                                                                                                                                            this.gadgetQueue = gadgetQueue;
                                                                                                                                                            this.dunnoQueue = dunnoQueue;
                                                                                                                                                    
                                                                                                                                                            inQueue.ReceiveCompleted += 新
                                                                                                                                                    图形/ccc.gifReceiveCompletedEventHandler(OnMessage);
                                                                                                                                                            inQueue.BeginReceive();
                                                                                                                                                        }
                                                                                                                                                    
                                                                                                                                                        私有无效OnMessage(对象源,
                                                                                                                                                    图形/ccc.gifReceiveCompletedEventArgs asyncResult)
                                                                                                                                                        {
                                                                                                                                                    
                                                                                                                                                            消息队列 mq = (消息队列)源;
                                                                                                                                                            mq.Formatter = 新 System.Messaging.XmlMessageFormatter
                                                                                                                                                                               (new String[] {"System.String,mscorlib"});
                                                                                                                                                            消息消息 = mq.EndReceive(asyncResult.AsyncResult);
                                                                                                                                                    
                                                                                                                                                            if (IsWidgetMessage(消息))
                                                                                                                                                                widgetQueue.Send(消息);
                                                                                                                                                            else if (IsGadgetMessage(消息))
                                                                                                                                                                gadgetQueue.Send(消息);
                                                                                                                                                            别的
                                                                                                                                                                不知道队列.Send(消息);
                                                                                                                                                            mq.BeginReceive();
                                                                                                                                                        }
                                                                                                                                                    
                                                                                                                                                        protected bool IsWidgetMessage (Message消息)
                                                                                                                                                        {
                                                                                                                                                            字符串文本 = (String)message.Body;
                                                                                                                                                            return (text.StartsWith("W"));
                                                                                                                                                        }
                                                                                                                                                    
                                                                                                                                                        protected bool IsGadgetMessage (Message 消息)
                                                                                                                                                        {
                                                                                                                                                            字符串文本 = (String)message.Body;
                                                                                                                                                            return (text.StartsWith("G"));
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                    
                                                                                                                                                    
                                                                                                                                                    class Con­tent­Based­Router
                                                                                                                                                    {
                                                                                                                                                        pro­tec­ted Mes­sageQueue in­Queue;
                                                                                                                                                        pro­tec­ted Mes­sageQueue wid­getQueue;
                                                                                                                                                        pro­tec­ted Mes­sageQueue gad­getQueue;
                                                                                                                                                        pro­tec­ted Mes­sageQueue dun­noQueue;
                                                                                                                                                    
                                                                                                                                                        public Con­tent­Based­Router(Mes­sageQueue in­Queue, Mes­sageQueue
                                                                                                                                                     wid­getQueue,
                                                                                                                                                                                  Mes­sageQueue gad­getQueue,
                                                                                                                                                     Mes­sageQueue dun­noQueue)
                                                                                                                                                        {
                                                                                                                                                            this.in­Queue = in­Queue;
                                                                                                                                                            this.wid­getQueue = wid­getQueue;
                                                                                                                                                            this.gad­getQueue = gad­getQueue;
                                                                                                                                                            this.dun­noQueue = dun­noQueue;
                                                                                                                                                    
                                                                                                                                                            in­Queue.Re­ceive­Com­pleted += new
                                                                                                                                                     Re­ceive­Com­plete­dE­ventHand­ler(On­Mes­sage);
                                                                                                                                                            in­Queue.Be­gin­Re­ceive();
                                                                                                                                                        }
                                                                                                                                                    
                                                                                                                                                        private void On­Mes­sage(Object source,
                                                                                                                                                     Re­ceive­Com­plete­dEvent­Args asyn­cRes­ult)
                                                                                                                                                        {
                                                                                                                                                    
                                                                                                                                                            Mes­sageQueue mq = (Mes­sageQueue)source;
                                                                                                                                                            mq.Format­ter = new System.Mes­saging.Xm­lMes­sage­Format­ter
                                                                                                                                                                               (new String[] {"System.String,mscorlib"});
                                                                                                                                                            Mes­sage mes­sage = mq.En­dRe­ceive(asyn­cRes­ult.Asyn­cRes­ult);
                                                                                                                                                    
                                                                                                                                                            if (IsWid­get­Mes­sage(mes­sage))
                                                                                                                                                                wid­getQueue.Send(mes­sage);
                                                                                                                                                            else if (Is­Gad­get­Mes­sage(mes­sage))
                                                                                                                                                                gad­getQueue.Send(mes­sage);
                                                                                                                                                            else
                                                                                                                                                                dun­noQueue.Send(mes­sage);
                                                                                                                                                            mq.Be­gin­Re­ceive();
                                                                                                                                                        }
                                                                                                                                                    
                                                                                                                                                        pro­tec­ted bool IsWid­get­Mes­sage (Mes­sage mes­sage)
                                                                                                                                                        {
                                                                                                                                                            String text = (String)mes­sage.Body;
                                                                                                                                                            return (text.StartsWith("W"));
                                                                                                                                                        }
                                                                                                                                                    
                                                                                                                                                        pro­tec­ted bool Is­Gad­get­Mes­sage (Mes­sage mes­sage)
                                                                                                                                                        {
                                                                                                                                                            String text = (String)mes­sage.Body;
                                                                                                                                                            return (text.StartsWith("G"));
                                                                                                                                                        }
                                                                                                                                                    }
                                                                                                                                                    

                                                                                                                                                    该示例通过将方法OnMessage注册为到达inQueue 的消息的处理程序来使用事件驱动的消息使用者。这会导致 .NET Framework 为到达inQueue调用OnMessage方法。消息队列Formatter属性告诉框架期望什么类型的消息;在我们的示例中,我们只处理简单的字符串消息。OnMessage确定消息的路由位置,并通过调用以下函数告诉 .NET 它已准备好接收下一条消息:确定消息的路由位置,并通过调用BeginReceive告诉 .NET 它已准备好接收下一条消息队列上的方法。为了使代码保持最少,这个简单的路由器不是事务性的:如果路由器在消耗来自输入通道的消息之后并将其发布到输出通道之前崩溃,我们将丢失一条消息。后面的章节解释了如何使端点具有事务性(请参阅事务性客户端)。

                                                                                                                                                    The ex­ample uses an event-driven mes­sage con­sumer by re­gis­ter­ing the method On­Mes­sage as the hand­ler for mes­sages ar­riv­ing on the in­Queue. This causes the .NET Frame­work to invoke the method On­Mes­sage for every mes­sage that ar­rives on the in­Queue. The mes­sage queue Format­ter prop­erty tells the frame­work what type of mes­sage to expect; in our ex­ample, we only deal with simple string mes­sages. On­Mes­sage fig­ures out where to route the mes­sage and tells .NET that it is ready for the next mes­sage by call­ing the Be­gin­Re­ceive method on the queue. In order to keep the code to a min­imum, this simple router is not trans­ac­tional: If the router crashes after it con­sumed a mes­sage from the input chan­nel and before it pub­lished it to the output chan­nel, we would lose a mes­sage. Later chapters ex­plain how to make en­d­points trans­ac­tional (see Trans­ac­tional Client ).



                                                                                                                                                    示例: TIBCO MessageBroker

                                                                                                                                                    Ex­ample: TIBCO Mes­sageBroker

                                                                                                                                                    消息路由是一种常见的需求,大多数 EAI 工具套件都提供内置工具来简化路由逻辑的构建。例如,在 C# 示例中,我们必须编写逻辑以从传入队列中读取消息、反序列化它、分析它,并将其重新发布到正确的传出通道。在许多 EAI 工具中,这种类型的逻辑可以通过简单的拖放操作来实现,而不是通过编写代码。唯一要编写的代码是基于内容的路由器的实际决策逻辑。

                                                                                                                                                    Mes­sage rout­ing is such a common need that most EAI tool suites provide built-in tools to sim­plify the con­struc­tion of the rout­ing logic. For ex­ample, in the C# ex­ample we had to code the logic to read a mes­sage off the in­com­ing queue, deseri­al­ize it, ana­lyze it, and re­pub­lish it to the cor­rect out­go­ing chan­nel. In many EAI tools this type of logic can be im­ple­men­ted with simple drag-and-drop op­er­a­tions in­stead of by writ­ing code. The only code to write is the actual de­cision logic for the Con­tent-Based Router.

                                                                                                                                                    TIBCO ActiveEnterprise 套件就是实现消息路由的此类 EAI 工具之一。该套件包括 TIB/MessageBroker,旨在创建包含转换和路由功能的简单消息流。在 TIB/MessageBroker 中实现时,根据项目编号的第一个字母路由传入消息的相同小部件路由器如下所示:

                                                                                                                                                    One such EAI tool that im­ple­ments mes­sage rout­ing is the TIBCO Act­iveEn­ter­prise suite. The suite in­cludes TIB/Mes­sageBroker, which is de­signed to create simple mes­sage flows that in­clude trans­form­a­tion and rout­ing func­tions. The same widget router that routes in­com­ing mes­sages based on the first letter of the item number looks like this when im­ple­men­ted in TIB/Mes­sageBroker:

                                                                                                                                                    图形/07inf03.jpg

                                                                                                                                                    我们可以从左到右阅读消息流。左侧的组件(由指向右侧的三角形表示)是订阅者组件,用于消费来自通道router.in的消息。通道名称在图中未显示的属性框中指定。消息内容被定向到消息发布者(由屏幕右侧的三角形表示)。从订阅者的数据输出到发布者的消息输入的直接线路表示基于内容的路由器不会修改消息正文。为了确定正确的输出通道,该函数ComputeSubject(中间)分析消息内容。该函数使用所谓的字典(图中标记为“Map”)作为消息内容和目标通道名称之间的转换表。该字典配置有以下值:

                                                                                                                                                    We can read the mes­sage flow from left to right. The com­pon­ent on the left (rep­res­en­ted by a tri­angle point­ing to the right) is the sub­scriber com­pon­ent that con­sumes mes­sages off the chan­nel router.in. The chan­nel name is spe­cified in a prop­er­ties box not shown in this figure. The mes­sage con­tent is dir­ec­ted to the mes­sage pub­lisher (rep­res­en­ted by the tri­angle on the right side of the screen). The direct line from the Data output of the sub­scriber to the Mes­sage input of the pub­lisher rep­res­ents the fact that a Con­tent-Based Router does not modify the mes­sage body. In order to de­term­ine the cor­rect output chan­nel, the func­tion Com­pute­Sub­ject (in the middle) ana­lyzes the mes­sage con­tent. The func­tion uses a so-called dic­tion­ary (labeled "Map" in the figure) as a trans­la­tion table between mes­sage con­tents and the des­tin­a­tion chan­nel name. The dic­tion­ary is con­figured with the fol­low­ing values:

                                                                                                                                                    项目代码

                                                                                                                                                    Item Code

                                                                                                                                                    频道名称

                                                                                                                                                    Chan­nel Name

                                                                                                                                                    G

                                                                                                                                                    G

                                                                                                                                                    小工具

                                                                                                                                                    gadget

                                                                                                                                                    W

                                                                                                                                                    小部件

                                                                                                                                                    widget

                                                                                                                                                    ComputeSubject函数使用传入消息的订单项编号的第一个字母从字典中查找目标通道。为了形成输出通道的完整名称,它将字典结果附加到字符串router.out ,以形成类似router.out.widget 的通道名称。计算结果被传递到右侧的发布者组件,用作通道的名称。因此,任何商品编号以 G 开头的订单商品都会被路由到通道router.out.gadget,而任何商品编号以 W 开头的商品都会被路由到通道router.out.widget

                                                                                                                                                    The Com­pute­Sub­ject func­tion uses the first letter of the in­com­ing mes­sage's order item number to look up the des­tin­a­tion chan­nel from the dic­tion­ary. To form the com­plete name of the output chan­nel, it ap­pends the dic­tion­ary result to the string router.out, to form a chan­nel name like router.out.widget. The result of this com­pu­ta­tion is passed to the pub­lisher com­pon­ent on the right to be used as the name of the chan­nel. As a result, any order item whose item number starts with a G is routed to the chan­nel router.out.gadget, whereas any item whose item number starts with a W is routed to the chan­nel router.out.widget.

                                                                                                                                                    ComputeSubject函数的 TIBCO 实现如下所示:

                                                                                                                                                    The TIBCO im­ple­ment­a­tion of the Com­pute­Sub­ject func­tion looks like this:

                                                                                                                                                    concat("router.out.",DGet(地图,Upper(Left(OrderItem.ItemNumber,1))))
                                                                                                                                                    
                                                                                                                                                    concat("router.out.",DGet(map,Upper(Left(Or­der­Item.Item­Num­ber,1))))
                                                                                                                                                    

                                                                                                                                                    该函数提取订单号的第一个字母(使用Left函数)并将其转换为大写字母(使用Upper函数)。该函数使用结果作为字典的键来检索传出通道的名称(使用DGet函数)。

                                                                                                                                                    The func­tion ex­tracts the first letter of the order number (using the Left func­tion) and con­verts it to up­per­case (using the Upper func­tion). The func­tion uses the result as the key to the dic­tion­ary to re­trieve the name of the out­go­ing chan­nel (using the DGet func­tion).

                                                                                                                                                    此示例展示了商业 EAI 工具的优势。我们不需要编写几十行代码,只需编写一个函数即可实现相同的小部件路由器功能。此外,我们还免费获得事务性、线程管理和系统管理等功能。但这个例子也凸显了呈现使用图形工具创建的解决方案的困难。我们不得不使用屏幕截图来描述解决方案。许多重要的设置隐藏在屏幕上未显示的属性字段中。这使得记录使用图形开发工具构建的解决方案变得困难。

                                                                                                                                                    This ex­ample demon­strates the strengths of com­mer­cial EAI tools. In­stead of a few dozen lines of code, we need to code only a single func­tion to im­ple­ment the same widget router func­tion­al­ity. Plus, we get fea­tures like trans­ac­tion­al­ity, thread man­age­ment, and sys­tems man­age­ment for free. But this ex­ample also high­lights the dif­fi­culties of present­ing a solu­tion cre­ated with graph­ical tools. We had to re­leg­ate to screen­shots to de­scribe the solu­tion. Many im­port­ant set­tings are hidden in prop­erty fields that are not shown on the screen. This can make it dif­fi­cult to doc­u­ment a solu­tion built using graph­ical de­vel­op­ment tools.



                                                                                                                                                      消息过滤器

                                                                                                                                                      Message Filter

                                                                                                                                                      图形/messagefilter_icon.gif

                                                                                                                                                      继续订单处理示例,假设公司管理层决定向大客户发布价格变化和促销信息。每当商品价格发生变化时,我们都会发送消息通知客户。如果我们正在进行特别促销,例如 11 月份所有小部件 10% 的折扣,我们也会这样做。有些客户可能有兴趣接收仅与特定商品相关的价格更新或促销信息。例如,如果我主要购买小工具,我可能对了解小工具是否打折不感兴趣。

                                                                                                                                                      Con­tinu­ing with the order pro­cess­ing ex­ample, let's assume that com­pany man­age­ment de­cided to pub­lish price changes and pro­mo­tions to large cus­tom­ers. We would like to send a mes­sage to notify the cus­tomer whenever the price for an item changes. We do the same if we are run­ning a spe­cial pro­mo­tion, such as 10 per­cent off all wid­gets in the month of Novem­ber. Some cus­tom­ers may be in­ter­ested in re­ceiv­ing price up­dates or pro­mo­tions re­lated only to spe­cific items. For ex­ample, if I pur­chase primar­ily gad­gets, I may not be in­ter­ested in know­ing whether or not wid­gets are on sale.

                                                                                                                                                      组件如何避免接收无趣的消息?

                                                                                                                                                      How can a com­pon­ent avoid re­ceiv­ing un­in­ter­est­ing mes­sages?



                                                                                                                                                      组件仅接收相关消息的最基本方法是仅订阅那些携带相关消息的通道。此选项利用了发布-订阅通道的固有路由功能。 组件仅接收那些通过该组件订阅的通道传播的消息。例如,我们可以创建一个用于小部件更新的通道,另一个用于小工具更新的通道。然后,客户可以自由订阅一个或另一个频道或两者。这样做的优点是新订户无需对系统进行任何更改即可加入。但是,订阅发布-订阅频道通常仅限于简单的二元条件:如果组件订阅了某个通道,它将接收该通道上的所有消息。实现更细粒度的唯一方法是创建更多通道。如果我们处理多个参数的组合,通道的数量可能会迅速爆炸。例如,如果我们想让消费者接收所有宣布所有小部件或小工具降价超过 5%、10% 或 15% 的消息,我们就需要 6 个(2 个商品类型乘以 3 个阈值)渠道。这种方法最终将变得难以管理,并且由于分配的通道数量巨大而消耗大量资源。因此,我们需要寻找一种比频道订阅更灵活的解决方案。

                                                                                                                                                      The most basic way for a com­pon­ent to re­ceive only rel­ev­ant mes­sages is to sub­scribe only to those chan­nels that carry rel­ev­ant mes­sages. This option lever­ages the in­her­ent rout­ing abil­it­ies of Pub­lish-Sub­scribe Chan­nels. A com­pon­ent re­ceives only those mes­sages that travel through chan­nels to which the com­pon­ent sub­scribes. For ex­ample, we could create one chan­nel for widget up­dates and an­other one for gadget up­dates. Cus­tom­ers would then be free to sub­scribe to one or the other chan­nel or both. This has the ad­vant­age that new sub­scribers can join in without re­quir­ing any changes to the system. How­ever, sub­scrip­tion to Pub­lish-Sub­scribe Chan­nel is gen­er­ally lim­ited to a simple binary con­di­tion: If a com­pon­ent sub­scribes to a chan­nel, it re­ceives all mes­sages on that chan­nel. The only way to achieve finer gran­u­lar­ity is to create more chan­nels. If we are deal­ing with a com­bin­a­tion of mul­tiple para­met­ers, the number of chan­nels can quickly ex­plode. For ex­ample, if we want to allow con­sumers to re­ceive all mes­sages that an­nounce all price cuts of wid­gets or gad­gets by more than 5 per­cent, 10 per­cent, or 15 per­cent, we already need six (2 item types mul­ti­plied by 3 threshold values) chan­nels. This ap­proach would ul­ti­mately become dif­fi­cult to manage and will con­sume sig­ni­fic­ant re­sources due to the large number of al­loc­ated chan­nels. So, we need to look for a solu­tion that allows for more flex­ib­il­ity than chan­nel sub­scrip­tion allows.

                                                                                                                                                      我们还需要一个能够适应频繁变化的解决方案。例如,我们可以修改基于内容的路由器以将消息路由到多个目的地(收件人列表中描述的概念)。该预测路由器仅向每个接收者发送相关消息,因此接收者无需采取任何额外步骤。然而,现在我们给消息发起者带来了维护每个订阅者的偏好的负担。如果收件人列表或其偏好快速变化,该解决方案将成为维护的噩梦。

                                                                                                                                                      We also need a solu­tion that can ac­com­mod­ate fre­quent change. For ex­ample, we could modify a Con­tent-Based Router to route the mes­sage to more than one des­tin­a­tion (a concept de­scribed in the Re­cip­i­ent List ). This pre­dict­ive router sends only rel­ev­ant mes­sages to each re­cip­i­ent so that the re­cip­i­ent does not have to take any extra steps. How­ever, now we burden the mes­sage ori­gin­ator with main­tain­ing the pref­er­ences for each and every sub­scriber. If the list of re­cip­i­ents or their pref­er­ences change quickly, this solu­tion would prove to be a main­ten­ance night­mare.

                                                                                                                                                      我们可以简单地将更改广播到所有组件,并期望每个组件过滤掉不需要的消息。然而,这种方法假设我们可以控制实际组件。在许多集成场景中,情况并非如此,因为我们处理的是打包应用程序、遗留应用程序或不受我们组织控制的应用程序。

                                                                                                                                                      We could simply broad­cast the changes to all com­pon­ents and expect each com­pon­ent to filter out the un­desir­able mes­sages. How­ever, this ap­proach as­sumes that we have con­trol over the actual com­pon­ent. In many in­teg­ra­tion scen­arios this is not the case be­cause we deal with pack­aged ap­plic­a­tions, legacy ap­plic­a­tions, or ap­plic­a­tions that are not under the con­trol of our or­gan­iz­a­tion.

                                                                                                                                                      使用一种特殊的消息路由器(消息过滤器),根据一组标准从通道中消除不需要的消息。

                                                                                                                                                      Use a spe­cial kind of Mes­sage Router, a Mes­sage Filter, to elim­in­ate un­de­sired mes­sages from a chan­nel based on a set of cri­teria.

                                                                                                                                                      图形/07inf04.gif



                                                                                                                                                      消息过滤器是具有单个输出通道的消息路由器。如果传入消息的内容与消息过滤器指定的条件匹配,则消息将被路由到输出通道。如果消息内容不符合条件,则该消息将被丢弃。

                                                                                                                                                      The Mes­sage Filter is a Mes­sage Router with a single output chan­nel. If the con­tent of an in­com­ing mes­sage matches the cri­teria spe­cified by the Mes­sage Filter, the mes­sage is routed to the output chan­nel. If the mes­sage con­tent does not match the cri­teria, the mes­sage is dis­carded.

                                                                                                                                                      在我们的示例中,我们将定义一个发布-订阅频道,每个客户都可以自由收听。然后,客户可以使用消息过滤器根据他或她选择的标准(例如商品类型或价格变化幅度)消除消息。

                                                                                                                                                      In our ex­ample we would define a single Pub­lish-Sub­scribe Chan­nel that each cus­tomer is free to listen on. The cus­tomer can then use a Mes­sage Filter to elim­in­ate mes­sages based on cri­teria of his or her choos­ing, such as the type of item or the mag­nitude of the price change.

                                                                                                                                                      消息过滤器可以被描述为基于内容的路由器的特殊情况,它将消息路由到输出通道或空通道,即丢弃发布到它的任何消息的通道。这种通道的用途类似于许多操作系统中存在的/dev/null目标或Null 对象[ PLoPD3 ]。

                                                                                                                                                      The Mes­sage Filter can be por­trayed as a spe­cial case of a Con­tent-Based Router that routes the mes­sage either to the output chan­nel or the null chan­nel, a chan­nel that dis­cards any mes­sage pub­lished to it. The pur­pose of such a chan­nel is sim­ilar to the /dev/null des­tin­a­tion present in many op­er­at­ing sys­tems or to a Null Object [PLoPD3].

                                                                                                                                                      无状态与有状态消息过滤器

                                                                                                                                                      State­less versus State­ful Mes­sage Fil­ters

                                                                                                                                                      小部件和小工具示例描述了无状态消息过滤器消息过滤器检查单个消息并仅根据该消息中包含的信息决定是否传递它。因此,消息过滤器不需要跨消息维护状态,被认为是无状态的。无状态组件的优点是它们允许我们并行运行组件的多个实例以加快处理速度。然而,消息过滤器不必是无状态的。例如,在某些情况下,消息过滤器需要跟踪消息历史记录。一个常见的场景是使用消息过滤器以消除重复的消息。假设每条消息都有唯一的消息标识符,消息过滤器将存储过去消息的标识符,以便它可以通过将每条消息的标识符与存储的标识符列表进行比较来识别重复消息。

                                                                                                                                                      The widget and gadget ex­ample de­scribes a state­less Mes­sage Filter: The Mes­sage Filter in­spects a single mes­sage and de­cides whether or not to pass it on based solely on in­form­a­tion con­tained in that mes­sage. There­fore, the Mes­sage Filter does not need to main­tain state across mes­sages and is con­sidered state­less. State­less com­pon­ents have the ad­vant­age that they allow us to run mul­tiple in­stances of the com­pon­ent in par­al­lel to speed up pro­cess­ing. How­ever, a Mes­sage Filter does not have to be state­less. For ex­ample, there are situ­ations in which the Mes­sage Filter needs to keep track of the mes­sage his­tory. A common scen­ario is the use of a Mes­sage Filter to elim­in­ate du­plic­ate mes­sages. As­sum­ing that each mes­sage has a unique mes­sage iden­ti­fier, the Mes­sage Filter would store the iden­ti­fi­ers of past mes­sages so that it can re­cog­nize a du­plic­ate mes­sage by com­par­ing each mes­sage's iden­ti­fier with the list of stored iden­ti­fi­ers.

                                                                                                                                                      消息传递系统中内置的过滤功能

                                                                                                                                                      Fil­ter­ing Func­tions Built into Mes­saging Sys­tems

                                                                                                                                                      一些消息传递系统在消息传递基础设施内合并了消息过滤器的各个方面。例如,某些发布-订阅系统允许您定义发布-订阅通道的层次结构。许多发布-订阅系统(包括大多数 JMS 实现)都允许这样做。例如,可以将促销活动发布到频道wgco.update.promotion.widget。然后,订阅者可以使用通配符来订阅特定的消息子集;例如,如果订阅者收听主题wgco.update.*.widget,他将收到与小部件相关的所有更新(促销和价格变化)。其他订阅者可能会收听 wgco.update.promotion.*,这将提供与小部件和小工具相关的所有促销活动,但没有价格变化。通道层次结构使我们能够通过附加限定参数来细化通道的语义,这样客户就可以通过指定附加条件作为通道名称的一部分来过滤消息,而不是订阅所有更新。然而,与消息过滤器相比,分层通道命名提供的灵活性仍然有限。例如,消息过滤器可以决定仅在价格变化超过 11.5% 时才传递价格变化消息,而这很难通过通道名称来表达。

                                                                                                                                                      Some mes­saging sys­tems in­cor­por­ate as­pects of a Mes­sage Filter inside the mes­saging in­fra­struc­ture. For ex­ample, some pub­lish-sub­scribe sys­tems allow you to define a hier­arch­ical struc­ture for Pub­lish-Sub­scribe Chan­nels. Many pub­lish-sub­scribe sys­tems, in­clud­ing most JMS im­ple­ment­a­tions, allow this. For ex­ample, one can pub­lish pro­mo­tions to the chan­nel wgco.update.pro­mo­tion.widget. A sub­scriber can then use wild­cards to sub­scribe to a spe­cific subset of mes­sages; for ex­ample, if a sub­scriber listens to the topic wgco.update.*.widget, he would re­ceive all up­dates (pro­mo­tions and price changes) re­lated to wid­gets. An­other sub­scriber may listen to wgco.update.pro­mo­tion.*, which would de­liver all pro­mo­tions re­lated to wid­gets and gad­gets, but no price changes. The chan­nel hier­archy lets us refine the se­mantics of a chan­nel by ap­pend­ing qual­i­fy­ing para­met­ers, so that in­stead of a cus­tomer sub­scrib­ing to all up­dates, cus­tom­ers can filter mes­sages by spe­cify­ing ad­di­tional cri­teria as part of the chan­nel name. How­ever, the flex­ib­il­ity provided by the hier­arch­ical chan­nel naming is still lim­ited when com­pared to a Mes­sage Filter. For ex­ample, a Mes­sage Filter could decide to pass on a price change mes­sage only if the price changed by more than 11.5 per­cent, some­thing that would be hard to ex­press by means of chan­nel names.

                                                                                                                                                      其他消息传递系统为接收应用程序内的选择性消费者提供 API 支持。消息选择器是在应用程序查看消息之前评估传入消息内的标头或属性元素的表达式。如果条件的计算结果不为 true,则该消息将被忽略并且不会传递到应用程序逻辑。消息选择器充当应用程序中内置的消息过滤器。虽然使用消息选择器仍然需要修改应用程序(这在 EAI 中通常是不可能的),但选择规则的执行内置于消息传递基础结构中。消息过滤器a和 a之间的一个重要区别Selective Consumer是指使用Selective不会消费不符合指定条件的消息。另一方面,消息过滤器从输入通道中删除所有消息,仅将那些符合指定条件的消息发布到输出通道。

                                                                                                                                                      Other mes­saging sys­tems provide API sup­port for Se­lect­ive Con­sumers inside the re­ceiv­ing ap­plic­a­tion. Mes­sage se­lect­ors are ex­pres­sions that eval­u­ate header or prop­erty ele­ments inside an in­com­ing mes­sage before the ap­plic­a­tion gets to see the mes­sage. If the con­di­tion does not eval­u­ate to true, the mes­sage is ig­nored and not passed on to the ap­plic­a­tion logic. A mes­sage se­lector acts as a Mes­sage Filter that is built into the ap­plic­a­tion. While the use of a mes­sage se­lector still re­quires you to modify the ap­plic­a­tion (some­thing that is often not pos­sible in EAI), the ex­e­cu­tion of the se­lec­tion rules is built into the mes­saging in­fra­struc­ture. One im­port­ant dif­fer­ence between a Mes­sage Filter and a Se­lect­ive Con­sumer is that a con­sumer using a Se­lect­ive Con­sumer does not con­sume mes­sages that do not match the spe­cified cri­teria. On the other hand, a Mes­sage Filter re­moves all mes­sages from the input chan­nel, pub­lish­ing to the output chan­nel only those that match the spe­cified cri­teria.

                                                                                                                                                      由于选择性消费者向消息传递基础设施注册过滤器表达式,因此基础设施能够根据过滤器标准做出智能的内部路由决策。我们假设消息接收者位于与消息发送者不同的网段上(甚至跨越 Internet)。如果只是为了发现我们想要丢弃该消息而将消息一直路由到消息过滤器,那将是相当浪费的。另一方面,我们希望使用消息过滤器机制,以便收件人能够控制消息路由,而不是中央消息路由器。如果消息过滤器是消息传递基础设施向消息订阅者提供的 API 的一部分,基础设施可以自由地将过滤器表达式传播到更接近源的位置。这将保持对消息订阅者进行控制的初衷,但允许消息传递基础设施避免不必要的网络流量。此行为类似于动态收件人列表

                                                                                                                                                      Be­cause the Se­lect­ive Con­sumer re­gisters the filter ex­pres­sion with the mes­saging in­fra­struc­ture, the in­fra­struc­ture is able to make smart in­ternal rout­ing de­cisions based on the filter cri­teria. Let's assume that the mes­sage re­ceiver sits on a dif­fer­ent net­work seg­ment from the mes­sage ori­gin­ator (or even across the In­ter­net). It would be rather waste­ful to route the mes­sage all the way to the Mes­sage Filter just to find out that we want to dis­card the mes­sage. On the other hand, we want to use a Mes­sage Filter mech­an­ism so that the re­cip­i­ents have con­trol over the mes­sage rout­ing in­stead of a cent­ral Mes­sage Router. If the Mes­sage Filter is part of the API that the mes­saging in­fra­struc­ture provides to the mes­sage sub­scriber, the in­fra­struc­ture is free to propag­ate the filter ex­pres­sion closer to the source. This will main­tain the ori­ginal intent of keep­ing con­trol with the mes­sage sub­scriber, but allows the mes­saging in­fra­struc­ture to avoid un­ne­ces­sary net­work traffic. This be­ha­vior re­sembles that of a dy­namic Re­cip­i­ent List.

                                                                                                                                                      使用消息过滤器实现路由功能

                                                                                                                                                      Using Mes­sage Fil­ters to Im­ple­ment Rout­ing Func­tion­al­ity

                                                                                                                                                      我们可以使用连接到一组消息过滤器的发布-订阅通道来消除不需要的消息,以实现与基于内容。下图说明了这两个选项:

                                                                                                                                                      We can use a Pub­lish-Sub­scribe Chan­nel con­nec­ted to a set of Mes­sage Filters who elim­in­ate un­wanted mes­sages to im­ple­ment func­tion­al­ity equi­val­ent to that of a Con­tent-Based Router. The fol­low­ing dia­grams il­lus­trate the two op­tions:

                                                                                                                                                      选项 1:使用基于内容的路由器

                                                                                                                                                      Option 1: Using a Con­tent-Based Router

                                                                                                                                                      图形/07inf05.gif

                                                                                                                                                      在这个简单的示例中,我们有两个接收者:接收者Gadgets只对小工具消息感兴趣,而接收者Widgets只对小工具消息感兴趣。基于内容的路由器评估每条消息的内容并将其预测性地路由到适当的接收者。

                                                                                                                                                      In this simple ex­ample, we have two re­ceiv­ers: re­ceiver Gad­gets is only in­ter­ested in gadget mes­sages, while re­ceiver Wid­gets is only in­ter­ested in widget mes­sages. The Con­tent-Based Router eval­u­ates each mes­sage's con­tent and routes it pre­dict­ively to the ap­pro­pri­ate re­ceiver.

                                                                                                                                                      选项 2:使用广播通道和一组消息过滤器

                                                                                                                                                      Option 2: Using a Broad­cast Chan­nel and a Set of Mes­sage Fil­ters

                                                                                                                                                      图形/07inf06.gif

                                                                                                                                                      第二个选项将消息广播到发布-订阅通道。每个收件人都配备了消息过滤器,以消除不需要的消息。例如,小部件接收器使用小部件过滤器,仅允许小部件消息通过。

                                                                                                                                                      The second option broad­casts the mes­sage to a Pub­lish-Sub­scribe Chan­nel. Each re­cip­i­ent is equipped with a Mes­sage Filter to elim­in­ate un­wanted mes­sages. For ex­ample, the Wid­gets re­ceiver em­ploys a widget filter that lets only widget mes­sages pass.

                                                                                                                                                      下表描述了两种解决方案之间的一些差异:

                                                                                                                                                      The fol­low­ing table char­ac­ter­izes some of the dif­fer­ences between the two solu­tions:

                                                                                                                                                      基于内容的路由器

                                                                                                                                                      Con­tent-Based Router

                                                                                                                                                      带有消息过滤器的发布-订阅通道

                                                                                                                                                      Pub­lish-Sub­scribe Chan­nel with Mes­sage Fil­ters

                                                                                                                                                      每条消息只有一个消费者接收。

                                                                                                                                                      Ex­actly one con­sumer re­ceives each mes­sage.

                                                                                                                                                      多个消费者可以消费一条消息。

                                                                                                                                                      More than one con­sumer can con­sume a mes­sage.

                                                                                                                                                      中央控制和维护预测路由。

                                                                                                                                                      Cent­ral con­trol and main­ten­an­ce­pre­dict­ive rout­ing.

                                                                                                                                                      分布式控制和维护无功滤波。

                                                                                                                                                      Dis­trib­uted con­trol and main­ten­an­cere­act­ive fil­ter­ing.

                                                                                                                                                      路由器需要了解参与者。如果添加或删除参与者,则可能需要更新路由器。

                                                                                                                                                      Router needs to know about par­ti­cipants. Router may need to be up­dated if par­ti­cipants are added or re­moved.

                                                                                                                                                      无需参与者了解。添加或删除参与者很容易。

                                                                                                                                                      No know­ledge of par­ti­cipants re­quired. Adding or re­mov­ing par­ti­cipants is easy.

                                                                                                                                                      通常用于商业交易,例如订单。

                                                                                                                                                      Often used for busi­ness trans­ac­tions, e.g., orders.

                                                                                                                                                      通常用于事件通知或信息性消息。

                                                                                                                                                      Often used for event no­ti­fic­a­tions or in­form­a­tional mes­sages.

                                                                                                                                                      基于队列的通道通常更有效。

                                                                                                                                                      Gen­er­ally more ef­fi­cient with queue-based chan­nels.

                                                                                                                                                      通常,发布-订阅通道的效率更高。

                                                                                                                                                      Gen­er­ally more ef­fi­cient with pub­lish-sub­scribe chan­nels.

                                                                                                                                                      我们如何在这两个选项之间做出决定?在某些情况下,决策是由所需的功能驱动的;例如,如果我们需要多个接收者处理同一条消息的能力,我们需要使用带有消息过滤器的发布-订阅通道。但在大多数情况下,我们决定由哪一方控制(并需要维护)路由决策。我们想要保留中央控制权还是将其外包给接收者?如果消息包含只有某些收件人才能看到的敏感数据,我们需要使用基于内容的路由器我们不想相信其他收件人会过滤掉邮件。例如,假设我们向高级客户提供特别折扣:我们不会将这些折扣发送给非高级客户,并希望他们忽略这些特别优惠。

                                                                                                                                                      How do we decide between the two op­tions? In some cases, the de­cision is driven by the re­quired func­tion­al­ity; for ex­ample, if we need the abil­ity for mul­tiple re­cip­i­ents to pro­cess the same mes­sage, we need to use a Pub­lish-Sub­scribe Chan­nel with Mes­sage Filters. In most cases, though, we decide by which party has con­trol over (and needs to main­tain) the rout­ing de­cision. Do we want to keep cent­ral con­trol or farm it out to the re­cip­i­ents? If mes­sages con­tain sens­it­ive data that is only to be seen by cer­tain re­cip­i­ents, we need to use a Con­tent-Based Router we would not want to trust the other re­cip­i­ents to filter out mes­sages. For ex­ample, let's assume we offer spe­cial dis­counts to our premium cus­tom­ers: We would not send those to our non-premium cus­tom­ers and expect them to ignore these spe­cial offers.

                                                                                                                                                      网络流量考虑因素也可以推动决策。如果我们有一种有效的方法来广播信息(例如,在内部网络上使用 IP 多播),那么使用过滤器会非常有效,并且可以避免单个路由器的潜在瓶颈。然而,如果这些信息通过互联网路由,我们就仅限于点对点连接。在这种情况下,单个路由器的效率要高得多,因为它可以避免向所有参与者发送单独的消息,而不管他们的兴趣如何。如果我们想将控制权传递给收件人,但出于网络效率的原因需要使用路由器,我们可以使用动态收件人列表。这种类型的收件人列表充当动态路由器,允许接收者通过向路由器发送控制消息来表达他们的偏好。收件人列表将收件人首选项存储在数据库或规则库中。当消息到达时,收件人列表会将消息转发给条件与该消息匹配的所有感兴趣的收件人。

                                                                                                                                                      Net­work traffic con­sid­er­a­tions can drive the de­cision as well. If we have an ef­fi­cient way to broad­cast in­form­a­tion (e.g., using IP mul­tic­ast on an in­ternal net­work), using fil­ters can be very ef­fi­cient and avoids the po­ten­tial bot­tle­neck of a single router. How­ever, if this in­form­a­tion is routed over the In­ter­net, we are lim­ited to point-to-point con­nec­tions. In this case a single router is much more ef­fi­cient, as it avoids send­ing in­di­vidual mes­sages to all par­ti­cipants re­gard­less of their in­terests. If we want to pass con­trol to the re­cip­i­ents but need to use a router for reas­ons of net­work ef­fi­ciency, we can employ a dy­namic Re­cip­i­ent List. This type of Re­cip­i­ent List acts as a Dy­namic Router, al­low­ing re­cip­i­ents to ex­press their pref­er­ences via con­trol mes­sages to the router. The Re­cip­i­ent List stores the re­cip­i­ent pref­er­ences in a data­base or a rule base. When a mes­sage ar­rives, the Re­cip­i­ent List for­wards the mes­sage to all in­ter­ested re­cip­i­ents whose cri­teria match the mes­sage.

                                                                                                                                                        动态路由器

                                                                                                                                                        Dynamic Router

                                                                                                                                                        图形/dynamicrouter_icon.gif

                                                                                                                                                        您正在使用消息路由器在多个目标之间路由消息。

                                                                                                                                                        You are using a Mes­sage Router to route mes­sages between mul­tiple des­tin­a­tions.

                                                                                                                                                        如何避免路由器对所有可能目的地的依赖,同时保持其效率?

                                                                                                                                                        How can you avoid the de­pend­ency of the router on all pos­sible des­tin­a­tions while main­tain­ing its ef­fi­ciency?



                                                                                                                                                        消息路由器非常高效,因为它可以将消息直接路由到正确的目的地。其他消息路由解决方案,尤其是反应式过滤解决方案,效率较低,因为它们使用试错方法:例如,路由表每条消息路由到第一个可能的目的地。如果该目的地正确,则它接受该消息;否则,消息将传递到第二个可能的目的地,依此类推。同样,基于消息过滤器的方法将消息发送给所有可能的收件人,无论他们是否感兴趣。

                                                                                                                                                        A Mes­sage Router is very ef­fi­cient be­cause it can route a mes­sage dir­ectly to the cor­rect des­tin­a­tion. Other solu­tions to mes­sage rout­ing, es­pe­cially re­act­ive fil­ter­ing solu­tions, are less ef­fi­cient be­cause they use a trial-and-error ap­proach: For ex­ample, a Rout­ing Slip routes each mes­sage to the first pos­sible des­tin­a­tion. If that des­tin­a­tion is the cor­rect one, it ac­cepts the mes­sage; oth­er­wise, the mes­sage is passed to the second pos­sible des­tin­a­tion and so on. Like­wise, a Mes­sage Filter based ap­proach sends the mes­sage to all pos­sible re­cip­i­ents whether they are in­ter­ested or not.

                                                                                                                                                        分布式路由解决方案还面临着消息有多个收件人或根本没有收件人的风险。除非我们使用中央路由元素,否则这两种情况都不会被发现。

                                                                                                                                                        Dis­trib­uted rout­ing solu­tions also suffer the risk that there are mul­tiple re­cip­i­ents of a mes­sage or none at all. Both situ­ations can go un­detec­ted unless we use a cent­ral rout­ing ele­ment.

                                                                                                                                                        为了达到这种准确性,我们需要使用消息路由器,其中包含有关每个目的地的知识以及将消息路由到目的地的规则。但是,如果可能的目的地列表频繁更改,这可能会给消息路由器带来维护负担。

                                                                                                                                                        In order to achieve this ac­cur­acy, we need to use a Mes­sage Router that in­cor­por­ates know­ledge about each des­tin­a­tion and the rules for rout­ing mes­sages to the des­tin­a­tions. How­ever, this can turn the Mes­sage Router into a main­ten­ance burden if the list of pos­sible des­tin­a­tions changes fre­quently.

                                                                                                                                                        使用动态路由器,该路由器可以根据来自参与目标的特殊配置消息进行自我配置。

                                                                                                                                                        Use a Dy­namic Router, a router that can self-con­fig­ure based on spe­cial con­fig­ur­a­tion mes­sages from par­ti­cip­at­ing des­tin­a­tions.

                                                                                                                                                        图形/07inf07.gif



                                                                                                                                                        除了通常的输入和输出通道之外,动态路由器还使用额外的控制通道。在系统启动期间,每个潜在接收者都会在此控制通道上向动态路由器发送一条特殊消息,宣布其存在并列出其可以处理消息的条件。动态路由器将每个参与者的首选项存储在规则库中。当消息到达时,动态路由器会评估所有规则并将消息路由到满足规则的收件人。这样可以实现高效的预测性路由,而无需动态路由器的维护依赖关于每个潜在的接收者。

                                                                                                                                                        Be­sides the usual input and output chan­nels, the Dy­namic Router uses an ad­di­tional con­trol chan­nel. During system star­tup, each po­ten­tial re­cip­i­ent sends a spe­cial mes­sage to the Dy­namic Router on this con­trol chan­nel, an­noun­cing its pres­ence and list­ing the con­di­tions under which it can handle a mes­sage. The Dy­namic Router stores the pref­er­ences for each par­ti­cipant in a rule base. When a mes­sage ar­rives, the Dy­namic Router eval­u­ates all rules and routes the mes­sage to the re­cip­i­ent whose rules are ful­filled. This allows for ef­fi­cient, pre­dict­ive rout­ing without the main­ten­ance de­pend­ency of the Dy­namic Router on each po­ten­tial re­cip­i­ent.

                                                                                                                                                        在最基本的场景中,每个参与者在启动时向动态路由器宣布其存在和路由首选项。这要求每个参与者了解动态路由器使用的控制队列。它还要求动态路由器以持久的方式存储规则。否则,如果动态路由器发生故障并必须重新启动,它将无法恢复路由规则。或者,动态路由器可以向所有可能的参与者发送广播消息,以触发他们回复控制消息。此配置更稳健,但需要使用额外的发布-订阅通道

                                                                                                                                                        In the most basic scen­ario, each par­ti­cipant an­nounces its ex­ist­ence and rout­ing pref­er­ences to the Dy­namic Router at star­tup time. This re­quires each par­ti­cipant to be aware of the con­trol queue used by the Dy­namic Router. It also re­quires the Dy­namic Router to store the rules in a per­sist­ent way. Oth­er­wise, if the Dy­namic Router fails and has to re­start, it would not be able to re­cover the rout­ing rules. Al­tern­at­ively, the Dy­namic Router could send a broad­cast mes­sage to all pos­sible par­ti­cipants to trig­ger them to reply with the con­trol mes­sage. This con­fig­ur­a­tion is more robust but re­quires the use of an ad­di­tional Pub­lish-Sub­scribe Chan­nel.

                                                                                                                                                        增强控制通道以允许参与者向动态路由器发送订阅和取消订阅消息可能是有意义的。这将允许接收者在运行时在路由方案中添加或删除自己。

                                                                                                                                                        It might make sense to en­hance the con­trol chan­nel to allow par­ti­cipants to send both sub­scribe and un­sub­scribe mes­sages to the Dy­namic Router. This would allow re­cip­i­ents to add or remove them­selves from the rout­ing scheme during runtime.

                                                                                                                                                        由于收件人彼此独立,动态路由器必须处理规则冲突,例如多个收件人宣布对同一类型的消息感兴趣。动态路由器可以采用多种不同的策略来解决此类冲突:

                                                                                                                                                        Be­cause the re­cip­i­ents are in­de­pend­ent from each other, the Dy­namic Router has to deal with rules con­flicts, such as mul­tiple re­cip­i­ents an­noun­cing in­terest in the same type of mes­sage. The Dy­namic Router can employ a number of dif­fer­ent strategies to re­solve such con­flicts:

                                                                                                                                                        1. 忽略与现有消息冲突的控制消息。此选项可确保路由规则不发生冲突。然而,路由表的状态可能取决于潜在接收者启动的顺序。如果所有接收者同时启动,这可能会导致不可预测的行为,因为所有接收者都会同时向控制队列宣布他们的首选项。

                                                                                                                                                        2. Ignore con­trol mes­sages that con­flict with ex­ist­ing mes­sages. This option as­sures that the rout­ing rules are free of con­flict. How­ever, the state of the rout­ing table may depend on the se­quence in which the po­ten­tial re­cip­i­ents start up. If all re­cip­i­ents start up at the same time, this may lead to un­pre­dict­able be­ha­vior be­cause all re­cip­i­ents would an­nounce their pref­er­ences at the same time to the con­trol queue.

                                                                                                                                                        3. 将消息发送给第一个符合条件的收件人。此选项允许路由表包含冲突,但会在消息传入时解决它们。

                                                                                                                                                        4. Send the mes­sage to the first re­cip­i­ent whose cri­teria match. This option allows the rout­ing table to con­tain con­flicts but re­solves them as mes­sages come in.

                                                                                                                                                        5. 将消息发送给所有符合条件的收件人。此选项可以容忍冲突,但会将动态路由器变成收件人列表一般来说,基于内容的路由器的行为意味着它为每条输入消息发布一条输出消息。该策略违反了该规则。

                                                                                                                                                        6. Send the mes­sage to all re­cip­i­ents whose cri­teria match. This option is tol­er­ant of con­flicts but turns the Dy­namic Router into a Re­cip­i­ent List. Gen­er­ally, the be­ha­vior of a Con­tent-Based Router im­plies that it pub­lishes one output mes­sage for each input mes­sage. This strategy vi­ol­ates that rule.

                                                                                                                                                        动态路由器的主要缺点是解决方案的复杂性和调试动态配置系统的难度。

                                                                                                                                                        The main li­ab­il­it­ies of the Dy­namic Router are the com­plex­ity of the solu­tion and the dif­fi­culty of de­bug­ging a dy­nam­ic­ally con­figured system.

                                                                                                                                                        动态路由器是另一个示例,其中基于消息的中间件执行与较低级别 IP 网络类似的功能。动态路由器的工作方式与 IP 路由中使用的动态路由表非常相似,用于在网络之间路由 IP 数据包。接收者用于配置动态路由器的协议类似于 IP 路由信息协议(RIP;有关详细信息,请参阅 [ Stevens ])。

                                                                                                                                                        A Dy­namic Router is an­other ex­ample where mes­sage-based mid­dle­ware per­forms sim­ilar func­tions to lower level IP net­work­ing. A Dy­namic Router works very sim­il­arly to the dy­namic rout­ing tables used in IP rout­ing to route IP pack­ets between net­works. The pro­tocol used by the re­cip­i­ents to con­fig­ure the Dy­namic Router is ana­log­ous to the IP Rout­ing In­form­a­tion Pro­tocol (RIP; for more in­form­a­tion see [Stevens]).

                                                                                                                                                        动态路由器的常见用途是面向服务的体系结构中的动态服务发现。如果客户端应用程序想要访问服务,它会向动态路由器发送一条包含服务名称的消息。动态路由器维护一个服务目录、所有服务及其名称和侦听通道的列表。路由器根据来自每个服务提供商的控制消息构建此目录。当服务请求到达时,动态路由器使用服务目录按名称查找服务,然后将消息路由到正确的通道。此设置允许客户端应用程序将命令消息发送到单个通道,而不必担心指定服务提供商的性质或位置,即使提供商发生变化也是如此。

                                                                                                                                                        A common use of the Dy­namic Router is dy­namic ser­vice dis­cov­ery in ser­vice-ori­ented ar­chi­tec­tures. If a client ap­plic­a­tion wants to access a ser­vice, it sends a mes­sage con­tain­ing the name of the ser­vice to the Dy­namic Router. The Dy­namic Router main­tains a ser­vice dir­ect­ory, a list of all ser­vices with their name and the chan­nel they listen on. The router builds this dir­ect­ory based on con­trol mes­sages from each ser­vice pro­vider. When a ser­vice re­quest ar­rives, the Dy­namic Router uses the ser­vice dir­ect­ory to look up the ser­vice by name, then routes the mes­sage to the cor­rect chan­nel. This setup allows the client ap­plic­a­tion to send com­mand mes­sages to a single chan­nel without having to worry about the nature or loc­a­tion of the spe­cified ser­vice pro­vider, even if the pro­vider changes.

                                                                                                                                                        一个相关的模式,即客户端-调度程序-服务器模式 [ POSA ],允许客户端在不知道服务提供商的物理位置的情况下请求特定服务。调度程序使用注册服务列表在客户端和实现所请求服务的物理服务器之间建立连接。动态路由器执行类似的功能,但与调度程序不同,因为它可以使用比简单的表查找更智能的路由。

                                                                                                                                                        A re­lated pat­tern, the Client-Dis­patcher-Server pat­tern [POSA], allows a client to re­quest a spe­cific ser­vice without know­ing the phys­ical loc­a­tion of the ser­vice pro­vider. The dis­patcher uses a list of re­gistered ser­vices to es­tab­lish a con­nec­tion between the client and the phys­ical server im­ple­ment­ing the re­ques­ted ser­vice. The Dy­namic Router per­forms a sim­ilar func­tion but is dif­fer­ent from the dis­patcher in that it can use more in­tel­li­gent rout­ing than a simple table lookup.

                                                                                                                                                        示例: 使用 C# 和 MSMQ 的动态路由器

                                                                                                                                                        Ex­ample: Dy­namic Router Using C# and MSMQ

                                                                                                                                                        此示例基于基于内容的路由器中提供的示例构建,并对其进行了增强以充当动态路由器。新组件监听两个通道:inQueuecontrolQueue 。控制队列可以接收格式为X:QueueName的消息,从而使动态路由器将正文以字母 X 开头的所有消息路由到队列QueueName

                                                                                                                                                        This ex­ample builds on the ex­ample presen­ted in the Con­tent-Based Router and en­hances it to act as a Dy­namic Router. The new com­pon­ent listens on two chan­nels: the in­Queue and the con­trolQueue. The con­trol queue can re­ceive mes­sages of the format X:QueueName, caus­ing the Dy­namic Router to route all mes­sages whose body text begins with the letter X to the queue QueueName.

                                                                                                                                                        
                                                                                                                                                        动态路由器类
                                                                                                                                                        {
                                                                                                                                                            受保护的消息队列inQueue;
                                                                                                                                                            受保护的消息队列控制队列;
                                                                                                                                                            受保护的消息队列不知道队列;
                                                                                                                                                        
                                                                                                                                                            受保护的 IDictionary 路由表 = (IDictionary)(new
                                                                                                                                                        图形/ccc.gif哈希表());
                                                                                                                                                        
                                                                                                                                                        
                                                                                                                                                            公共 DynamicRouter(消息队列 inQueue,消息队列
                                                                                                                                                        图形/ccc.gif控制队列,
                                                                                                                                                                                 消息队列(不知道队列)
                                                                                                                                                            {
                                                                                                                                                                this.inQueue = inQueue;
                                                                                                                                                                this.controlQueue = controlQueue;
                                                                                                                                                                this.dunnoQueue = dunnoQueue;
                                                                                                                                                        
                                                                                                                                                                inQueue.ReceiveCompleted += 新
                                                                                                                                                        图形/ccc.gifReceiveCompletedEventHandler(OnMessage);
                                                                                                                                                                inQueue.BeginReceive();
                                                                                                                                                        
                                                                                                                                                                controlQueue.ReceiveCompleted +=
                                                                                                                                                                    新的 ReceiveCompletedEventHandler(OnControlMessage);
                                                                                                                                                                controlQueue.BeginReceive();
                                                                                                                                                            }
                                                                                                                                                        
                                                                                                                                                            protected void OnMessage(对象源,
                                                                                                                                                        图形/ccc.gifReceiveCompletedEventArgs asyncResult)
                                                                                                                                                            {
                                                                                                                                                                消息队列 mq = (消息队列)源;
                                                                                                                                                                mq.Formatter = 新 System.Messaging.XmlMessageFormatter
                                                                                                                                                                                   (new String[] {"System.String,mscorlib"});
                                                                                                                                                        
                                                                                                                                                                消息消息 = mq.EndReceive(asyncResult.AsyncResult);
                                                                                                                                                        
                                                                                                                                                                字符串键 = ((String)message.Body).Substring(0, 1);
                                                                                                                                                        
                                                                                                                                                                if (routingTable.Contains(key))
                                                                                                                                                                {
                                                                                                                                                                    消息队列目的地 =
                                                                                                                                                        图形/ccc.gif(MessageQueue)路由表[key];
                                                                                                                                                                    目的地.发送(消息);
                                                                                                                                                                }
                                                                                                                                                                别的
                                                                                                                                                                    不知道队列.Send(消息);
                                                                                                                                                                mq.BeginReceive();
                                                                                                                                                            }
                                                                                                                                                        
                                                                                                                                                            // 控制消息格式为 X:QueueName 作为单个字符串
                                                                                                                                                            protected void OnControlMessage(对象源,
                                                                                                                                                        图形/ccc.gifReceiveCompletedEventArgs asyncResult)
                                                                                                                                                            {
                                                                                                                                                                消息队列 mq = (消息队列)源;
                                                                                                                                                                mq.Formatter = 新 System.Messaging.XmlMessageFormatter
                                                                                                                                                                                   (new String[] {"System.String,mscorlib"});
                                                                                                                                                                消息消息 = mq.EndReceive(asyncResult.AsyncResult);
                                                                                                                                                        
                                                                                                                                                                字符串文本 = ((String)message.Body);
                                                                                                                                                                String[] split = (text.Split(new char[] {':'}, 2));
                                                                                                                                                                if (split.Length == 2)
                                                                                                                                                                {
                                                                                                                                                                    字符串键 = split[0];
                                                                                                                                                                    String 队列名称 = split[1];
                                                                                                                                                                    MessageQueue 队列 = FindQueue(queueName);
                                                                                                                                                                    路由表.Add(键,队列);
                                                                                                                                                                }
                                                                                                                                                                别的
                                                                                                                                                                {
                                                                                                                                                                    不知道队列.Send(消息);
                                                                                                                                                                }
                                                                                                                                                                mq.BeginReceive();
                                                                                                                                                            }
                                                                                                                                                        
                                                                                                                                                            protected MessageQueue FindQueue(字符串队列名称)
                                                                                                                                                            {
                                                                                                                                                                if (!MessageQueue.Exists(queueName))
                                                                                                                                                                {
                                                                                                                                                                    返回 MessageQueue.Create(queueName);
                                                                                                                                                                }
                                                                                                                                                                别的
                                                                                                                                                                    返回新的消息队列(队列名称);
                                                                                                                                                            }
                                                                                                                                                        }
                                                                                                                                                        
                                                                                                                                                        
                                                                                                                                                        class Dy­na­mic­Router
                                                                                                                                                        {
                                                                                                                                                            pro­tec­ted Mes­sageQueue in­Queue;
                                                                                                                                                            pro­tec­ted Mes­sageQueue con­trolQueue;
                                                                                                                                                            pro­tec­ted Mes­sageQueue dun­noQueue;
                                                                                                                                                        
                                                                                                                                                            pro­tec­ted IDic­tion­ary rout­ingT­able = (IDic­tion­ary)(new
                                                                                                                                                         Hasht­able());
                                                                                                                                                        
                                                                                                                                                        
                                                                                                                                                            public Dy­na­mic­Router(Mes­sageQueue in­Queue, Mes­sageQueue
                                                                                                                                                         con­trolQueue,
                                                                                                                                                                                 Mes­sageQueue dun­noQueue)
                                                                                                                                                            {
                                                                                                                                                                this.in­Queue = in­Queue;
                                                                                                                                                                this.con­trolQueue = con­trolQueue;
                                                                                                                                                                this.dun­noQueue = dun­noQueue;
                                                                                                                                                        
                                                                                                                                                                in­Queue.Re­ceive­Com­pleted += new
                                                                                                                                                         Re­ceive­Com­plete­dE­ventHand­ler(On­Mes­sage);
                                                                                                                                                                in­Queue.Be­gin­Re­ceive();
                                                                                                                                                        
                                                                                                                                                                con­trolQueue.Re­ceive­Com­pleted +=
                                                                                                                                                                    new Re­ceive­Com­plete­dE­ventHand­ler(On­Con­trolMes­sage);
                                                                                                                                                                con­trolQueue.Be­gin­Re­ceive();
                                                                                                                                                            }
                                                                                                                                                        
                                                                                                                                                            pro­tec­ted void On­Mes­sage(Object source,
                                                                                                                                                         Re­ceive­Com­plete­dEvent­Args asyn­cRes­ult)
                                                                                                                                                            {
                                                                                                                                                                Mes­sageQueue mq = (Mes­sageQueue)source;
                                                                                                                                                                mq.Format­ter = new System.Mes­saging.Xm­lMes­sage­Format­ter
                                                                                                                                                                                   (new String[] {"System.String,mscorlib"});
                                                                                                                                                        
                                                                                                                                                                Mes­sage mes­sage = mq.En­dRe­ceive(asyn­cRes­ult.Asyn­cRes­ult);
                                                                                                                                                        
                                                                                                                                                                String key = ((String)mes­sage.Body).Sub­string(0, 1);
                                                                                                                                                        
                                                                                                                                                                if (rout­ingT­able.Con­tains(key))
                                                                                                                                                                {
                                                                                                                                                                    Mes­sageQueue des­tin­a­tion  = 
                                                                                                                                                        (Mes­sageQueue)rout­ingT­able[key];
                                                                                                                                                                    des­tin­a­tion.Send(mes­sage);
                                                                                                                                                                }
                                                                                                                                                                else
                                                                                                                                                                    dun­noQueue.Send(mes­sage);
                                                                                                                                                                mq.Be­gin­Re­ceive();
                                                                                                                                                            }
                                                                                                                                                        
                                                                                                                                                            // con­trol mes­sage format is X:QueueName as a single string
                                                                                                                                                            pro­tec­ted void On­Con­trolMes­sage(Object source,
                                                                                                                                                         Re­ceive­Com­plete­dEvent­Args asyn­cRes­ult)
                                                                                                                                                            {
                                                                                                                                                                Mes­sageQueue mq = (Mes­sageQueue)source;
                                                                                                                                                                mq.Format­ter = new System.Mes­saging.Xm­lMes­sage­Format­ter
                                                                                                                                                                                   (new String[] {"System.String,mscorlib"});
                                                                                                                                                                Mes­sage mes­sage = mq.En­dRe­ceive(asyn­cRes­ult.Asyn­cRes­ult);
                                                                                                                                                        
                                                                                                                                                                String text = ((String)mes­sage.Body);
                                                                                                                                                                String [] split = (text.Split(new char[] {':'}, 2));
                                                                                                                                                                if (split.Length == 2)
                                                                                                                                                                {
                                                                                                                                                                    String key = split[0];
                                                                                                                                                                    String queueName = split[1];
                                                                                                                                                                    Mes­sageQueue queue = Find­Queue(queueName);
                                                                                                                                                                    rout­ingT­able.Add(key, queue);
                                                                                                                                                                }
                                                                                                                                                                else
                                                                                                                                                                {
                                                                                                                                                                    dun­noQueue.Send(mes­sage);
                                                                                                                                                                }
                                                                                                                                                                mq.Be­gin­Re­ceive();
                                                                                                                                                            }
                                                                                                                                                        
                                                                                                                                                            pro­tec­ted Mes­sageQueue Find­Queue(string queueName)
                                                                                                                                                            {
                                                                                                                                                                if (!Mes­sageQueue.Exists(queueName))
                                                                                                                                                                {
                                                                                                                                                                    return Mes­sageQueue.Create(queueName);
                                                                                                                                                                }
                                                                                                                                                                else
                                                                                                                                                                    return new Mes­sageQueue(queueName);
                                                                                                                                                            }
                                                                                                                                                        }
                                                                                                                                                        

                                                                                                                                                        这个例子使用了一个非常简单的冲突解决机制,最后一个获胜。如果两个收件人表示有兴趣接收以字母 X 开头的消息,则只有第二个收件人会收到该消息,因为哈希表只为每个键值存储一个队列。另请注意,dunnoQueue 现在可以接收两种类型的消息:没有匹配路由规则的传入消息或与所需格式不匹配的控制消息。

                                                                                                                                                        This ex­ample uses a very simple con­flict res­ol­u­tion mech­an­ismlast one wins. If two re­cip­i­ents ex­press in­terest in re­ceiv­ing mes­sages that start with the letter X, only the second re­cip­i­ent will re­ceive the mes­sage be­cause the Hasht­able stores only one queue for each key value. Also note that the dun­noQueue can now re­ceive two types of mes­sages: in­com­ing mes­sages that have no match­ing rout­ing rules or con­trol mes­sages that do not match the re­quired format.



                                                                                                                                                          收件人名单

                                                                                                                                                          Recipient List

                                                                                                                                                          图形/recipientlist_icon.gif

                                                                                                                                                          基于内容的路由器允许我们根据消息内容将消息路由到正确的系统。这个过程对于原始发送者来说是透明的,因为发起者只是将消息发送到一个通道,路由器在其中接收消息并处理所有事情。

                                                                                                                                                          A Con­tent-Based Router allows us to route a mes­sage to the cor­rect system based on mes­sage con­tent. This pro­cess is trans­par­ent to the ori­ginal sender in the sense that the ori­gin­ator simply sends the mes­sage to a chan­nel, where the router picks it up and takes care of everything.

                                                                                                                                                          但在某些情况下,我们可能希望为该消息指定一个或多个收件人。一个常见的类比是大多数电子邮件系统中实现的收件人列表。对于每封电子邮件,发件人可以指定收件人列表。然后,邮件系统确保将消息内容传输到每个收件人。企业集成领域的一个例子是一种功能可以由一个或多个提供商执行的情况。例如,我们可能与多个信用机构签订合同来评估客户的信用度。当收到小订单时,我们可以简单地将信用请求消息发送给一家信用机构。如果客户下了大订单,我们可能希望将信用请求消息发送给多个机构并在做出决定之前比较结果。在这种情况下,收件人列表取决于订单的美元价值。

                                                                                                                                                          In some cases, though, we may want to spe­cify one or more re­cip­i­ents for the mes­sage. A common ana­logy is the re­cip­i­ent lists im­ple­men­ted in most e-mail sys­tems. For each e-mail mes­sage, the sender can spe­cify a list of re­cip­i­ents. The mail system then en­sures trans­port of the mes­sage con­tent to each re­cip­i­ent. An ex­ample from the domain of en­ter­prise in­teg­ra­tion would be a situ­ation where a func­tion can be per­formed by one or more pro­viders. For ex­ample, we may have a con­tract with mul­tiple credit agen­cies to assess the credit wor­thi­ness of our cus­tom­ers. When a small order comes in, we may simply route the credit re­quest mes­sage to one credit agency. If a cus­tomer places a large order, we may want to route the credit re­quest mes­sage to mul­tiple agen­cies and com­pare the res­ults before making a de­cision. In this case, the list of re­cip­i­ents de­pends on the dollar value of the order.

                                                                                                                                                          在另一种情况下,我们可能希望将订单消息路由到选定的供应商列表,以获得所请求项目的报价。我们可能希望根据用户偏好来控制哪些供应商接收请求,而不是将请求发送给所有供应商。

                                                                                                                                                          In an­other situ­ation, we may want to route an order mes­sage to a select list of sup­pli­ers to obtain a quote for a re­ques­ted item. Rather than send­ing the re­quest to all vendors, we may want to con­trol which vendors re­ceive the re­quest, pos­sibly based on user pref­er­ences.

                                                                                                                                                          我们如何将消息路由到动态收件人列表?

                                                                                                                                                          How do we route a mes­sage to a dy­namic list of re­cip­i­ents?



                                                                                                                                                          这个问题是基于内容的路由器解决的问题的扩展,因此该模式中描述的一些相同的力量和替代方案也在这里发挥作用。

                                                                                                                                                          This prob­lem is an ex­ten­sion to the issue that a Con­tent-Based Router solves, so some of the same forces and al­tern­at­ives de­scribed in that pat­tern come into play here as well.

                                                                                                                                                          大多数消息传递系统都提供发布-订阅通道,这是一种将每条已发布消息的副本发送给订阅该通道的每个收件人的通道。收件人组基于对特定频道或主题的订阅。然而,频道的活动订阅者列表在某种程度上是静态的,并且不能逐条消息地进行控制。我们需要的是类似发布-订阅通道的东西,它可以将每条消息发送到不同的订阅者列表。这很困难,因为订阅发布-订阅频道是二进制的,您要么订阅频道上的所有消息,要么不订阅。

                                                                                                                                                          Most mes­saging sys­tems provide Pub­lish-Sub­scribe Chan­nels, a type of chan­nel that sends a copy of each pub­lished mes­sage to each re­cip­i­ent that sub­scribes to the chan­nel. The set of re­cip­i­ents is based on sub­scrip­tion to the spe­cific chan­nel or sub­ject. How­ever, the list of active sub­scribers to a chan­nel is some­what static and cannot be con­trolled on a mes­sage-by-mes­sage basis. What we need is some­thing like a Pub­lish-Sub­scribe Chan­nel that can send each mes­sage to a dif­fer­ent list of sub­scribers. This is dif­fi­cult be­cause sub­scrib­ing to a Pub­lish-Sub­scribe Chan­nel is bin­aryyou are either sub­scribed to all mes­sages on the chan­nel or none.

                                                                                                                                                          每个潜在的收件人都可以根据消息内容过滤传入的消息,最有可能使用消息过滤器选择性消费者。不幸的是,该解决方案将谁接收消息的逻辑分发给各个订阅者,这可能会成为维护负担。为了保留中心控制点,消息可以在附加到消息的列表中指定其预期接收者。然后,当消息被广播给所有可能的接收者时,不在该消息的接收者列表中的每个接收者将丢弃该消息。

                                                                                                                                                          Each po­ten­tial re­cip­i­ent could filter in­com­ing mes­sages based on mes­sage con­tent, most likely using a Mes­sage Filter or Se­lect­ive Con­sumer. Un­for­tu­nately, this solu­tion dis­trib­utes the logic of who re­ceives the mes­sage to the in­di­vidual sub­scribers, which could become a main­ten­ance burden. To retain a cent­ral point of con­trol, the mes­sage could spe­cify its in­ten­ded re­cip­i­ents in a list at­tached to the mes­sage. Then, when the mes­sage is broad­cast to all pos­sible re­cip­i­ents, each re­cip­i­ent that is not in the mes­sage's re­cip­i­ent list would dis­card the mes­sage.

                                                                                                                                                          这些方法的问题在于效率低下:每个潜在接收者都必须处理每条消息,即使在许多情况下接收者会决定丢弃该消息。该配置还依赖于接收者的某种“荣誉系统”,因为接收者可以决定处理它不应该处理的消息。在消息应该对某些接收者隐藏的情况下,这绝对是不可取的,例如,当将报价请求转发给选定的供应商子集并期望其他人在收到请求时忽略该请求时。

                                                                                                                                                          The prob­lem with these ap­proaches is their in­ef­fi­ciency: Each po­ten­tial re­cip­i­ent must pro­cess every mes­sage, even though in many cases the re­cip­i­ent will decide to dis­card the mes­sage. The con­fig­ur­a­tion also relies on a cer­tain "honor system" on the part of the re­cip­i­ents, as a re­cip­i­ent could decide to pro­cess a mes­sage it is not sup­posed to pro­cess. This is def­in­itely not de­sir­able in situ­ations where a mes­sage should be kept hidden from cer­tain re­cip­i­ents, for ex­ample when for­ward­ing a re­quest for a quote to a select subset of sup­pli­ers and ex­pect­ing the others to ignore the re­quest when they re­ceive it.

                                                                                                                                                          我们还可以要求消息发起者将消息单独发布给每个所需的接收者。但在这种情况下,我们会将向所有收件人传递的责任交给消息发起者。如果发起者是打包应用程序,那么这通常不是一个选项。此外,它将决策逻辑嵌入到应用程序中,这将使应用程序与集成基础设施更紧密地耦合。在许多情况下,正在集成的应用程序甚至不知道它们参与了集成解决方案,因此期望应用程序包含消息路由逻辑是不现实的。

                                                                                                                                                          We could also re­quire the mes­sage ori­gin­ator to pub­lish the mes­sage in­di­vidu­ally to each de­sired re­cip­i­ent. In that case, though, we would place the burden of de­liv­ery to all re­cip­i­ents on the mes­sage ori­gin­ator. If the ori­gin­ator is a pack­aged ap­plic­a­tion, this is gen­er­ally not an option. Also, it would embed de­cision logic inside the ap­plic­a­tion, which would couple the ap­plic­a­tion more tightly to the in­teg­ra­tion in­fra­struc­ture. In many cases, the ap­plic­a­tions that are being in­teg­rated are un­aware that they even par­ti­cip­ate in an in­teg­ra­tion solu­tion, so ex­pect­ing the ap­plic­a­tion to con­tain mes­sage rout­ing logic is not real­istic.

                                                                                                                                                          为每个接收者定义一个渠道。然后使用收件人列表检查传入消息,确定所需收件人的列表,并将消息转发到与列表中的收件人关联的所有通道。

                                                                                                                                                          Define a chan­nel for each re­cip­i­ent. Then use a Re­cip­i­ent List to in­spect an in­com­ing mes­sage, de­term­ine the list of de­sired re­cip­i­ents, and for­ward the mes­sage to all chan­nels as­so­ci­ated with the re­cip­i­ents in the list.

                                                                                                                                                          图形/07inf08.gif



                                                                                                                                                          尽管实现通常耦合在一起,但嵌入在收件人列表中的逻辑可以被描绘为两个独立的部分。第一部分计算收件人列表。第二部分只是遍历列表并将收到的消息的副本发送给每个收件人。就像基于内容的路由器一样,收件人列表通常不会修改消息内容。

                                                                                                                                                          The logic em­bed­ded in a Re­cip­i­ent List can be pic­tured as two sep­ar­ate parts even though the im­ple­ment­a­tion is often coupled to­gether. The first part com­putes a list of re­cip­i­ents. The second part simply tra­verses the list and sends a copy of the re­ceived mes­sage to each re­cip­i­ent. Just like a Con­tent-Based Router, the Re­cip­i­ent List usu­ally does not modify the mes­sage con­tents.

                                                                                                                                                          收件人列表可以计算收件人(左)或让另一个组件提供列表(右)

                                                                                                                                                          A Re­cip­i­ent List Can Com­pute the Re­cip­i­ents (left) or Have An­other Com­pon­ent Provide a List (right)

                                                                                                                                                          图形/07inf09.gif

                                                                                                                                                          接收者列表可以从多个来源获得。列表的创建可以在收件人列表外部,以便消息发起者或另一个组件将该列表附加到传入消息。在这种情况下,收件人列表只需迭代这个现成的列表。收件人列表通常会从邮件中删除列表,以减少传出邮件的大小并防止个别收件人看到列表中的其他人。或者,如果每条消息的目的地由外部因素(例如用户选择)驱动,则向传入消息提供收件人列表是有意义的。

                                                                                                                                                          The list of re­cip­i­ents can be de­rived from a number of sources. The cre­ation of the list can be ex­ternal to the Re­cip­i­ent List so that the mes­sage ori­gin­ator or an­other com­pon­ent at­taches the list to the in­com­ing mes­sage. In that case, the Re­cip­i­ent List only has to it­er­ate through this ready-made list. The Re­cip­i­ent List usu­ally re­moves the list from the mes­sage to reduce the size of the out­go­ing mes­sages and pre­vent in­di­vidual re­cip­i­ents from seeing who else is on the list. Al­tern­at­ively, provid­ing the list of re­cip­i­ents with the in­com­ing mes­sage makes sense if the des­tin­a­tions of each mes­sage are driven by ex­ternal factors, such as user se­lec­tion.

                                                                                                                                                          在大多数情况下,收件人列表根据邮件内容和收件人列表中嵌入的一组规则来计算收件人列表。这些规则可以是硬编码的或可配置的(参见下页)。

                                                                                                                                                          In most cases, the Re­cip­i­ent List com­putes the list of re­cip­i­ents based on the con­tent of the mes­sage and a set of rules em­bed­ded in the Re­cip­i­ent List. These rules may be hard-coded or con­fig­ur­able (see the fol­low­ing page).

                                                                                                                                                          收件人列表受到与消息路由器中讨论的有关耦合的相同注意事项的约束。将消息预测性地路由到各个接收者可能会导致组件之间的耦合更紧密,因为中央组件必须了解一系列其他组件。

                                                                                                                                                          The Re­cip­i­ent List is sub­ject to the same con­sid­er­a­tions re­gard­ing coup­ling as dis­cussed in Mes­sage Router. Rout­ing mes­sages pre­dict­ively to in­di­vidual re­cip­i­ents can lead to tighter coup­ling between com­pon­ents be­cause a cent­ral com­pon­ent has to have know­ledge of a series of other com­pon­ents.

                                                                                                                                                          为了让收件人列表控制信息流,我们需要确保收件人不能直接订阅到收件人列表的输入通道,从而绕过收件人列表执行的任何控制。

                                                                                                                                                          In order for the Re­cip­i­ent List to con­trol flow of in­form­a­tion, we need to make sure that re­cip­i­ents cannot sub­scribe dir­ectly to the input chan­nel into the Re­cip­i­ent List, by­passing any con­trol the Re­cip­i­ent List ex­er­cises.

                                                                                                                                                          鲁棒性

                                                                                                                                                          Ro­bust­ness

                                                                                                                                                          收件人列表组件负责将传入消息发送到收件人列表中指定的每个收件人。收件人列表的稳健实现必须能够处理传入消息,但仅在所有出站消息成功发送后才“使用”它。因此,接收者列表组件必须确保接收一条消息和发送多条消息的完整操作是原子的。如果收件人列表失败,它必须是可重新启动的,这意味着它必须能够完成组件失败时正在进行的任何操作。这可以通过多种方式完成:

                                                                                                                                                          The Re­cip­i­ent List com­pon­ent is re­spons­ible for send­ing the in­com­ing mes­sage to each re­cip­i­ent spe­cified in the re­cip­i­ent list. A robust im­ple­ment­a­tion of the Re­cip­i­ent List must be able to pro­cess the in­com­ing mes­sage but only "con­sume" it after all out­bound mes­sages have been suc­cess­fully sent. As such, the Re­cip­i­ent List com­pon­ent has to ensure that the com­plete op­er­a­tion of re­ceiv­ing one mes­sage and send­ing mul­tiple mes­sages is atomic. If a Re­cip­i­ent List fails, it must be re­start­able, mean­ing it must be able to com­plete any op­er­a­tion that was in pro­gress when the com­pon­ent failed. This can be ac­com­plished in mul­tiple ways:

                                                                                                                                                          1. 单个事务: 收件人列表可以使用事务通道并将消息作为单个事务的一部分放置在出站通道上。在所有消息都放置在通道上之前,它不会提交消息。这保证了要么发送所有消息,要么不发送消息。

                                                                                                                                                          2. Single trans­ac­tion: The Re­cip­i­ent List can use trans­ac­tional chan­nels and place the mes­sage on the out­bound chan­nels as part of a single trans­ac­tion. It does not commit the mes­sages until all mes­sages are placed on the chan­nels. This guar­an­tees that either all or no mes­sages are sent.

                                                                                                                                                          3. 持久收件人列表: 收件人列表可以“记住”它已经发送了哪些消息,以便在失败和重新启动时可以将消息发送给剩余的收件人。收件人列表可以存储在磁盘或数据库上,以便在收件人列表组件崩溃时仍能幸存。

                                                                                                                                                          4. Per­sist­ent re­cip­i­ent list: The Re­cip­i­ent List can "re­mem­ber" which mes­sages it already sent so that on fail­ure and re­start it can send mes­sages to the re­main­ing re­cip­i­ents. The re­cip­i­ent list could be stored on disk or a data­base so that it sur­vives a crash of the Re­cip­i­ent List com­pon­ent.

                                                                                                                                                          5. 幂等接收者: 或者,接收者列表可以简单地在重新启动时重新发送所有消息。此选项要求所有潜在接收者都是幂等的(请参阅幂等接收者) 。幂等函数是指那些应用到自身上不会改变系统状态的函数;也就是说,如果同一条消息被处理两次,组件的状态不会受到影响。消息本质上可以是幂等的(例如,消息“所有小部件在 5 月 30 日之前促销”或“给我获取 XYZ 小部件的报价”如果收到两次,则不太可能造成损害),或者可以通过以下方式使接收组件成为幂等的:插入特殊的消息过滤器这消除了重复的消息。幂等性非常方便,因为当我们怀疑收件人是否已收到消息时,它允许我们简单地重新发送消息。TCP/IP 协议使用类似的机制来确保可靠的消息传递,而无需不必要的开销(请参阅 [ Stevens ])。

                                                                                                                                                          6. Idem­potent re­ceiv­ers: Al­tern­at­ively, the Re­cip­i­ent List could simply resend all mes­sages on re­start. This option re­quires all po­ten­tial re­cip­i­ents to be idem­potent (see Idem­potent Re­ceiver ). Idem­potent func­tions are those that do not change the state of the system if they are ap­plied to them­selves; that is, the state of the com­pon­ent is not af­fected if the same mes­sage is pro­cessed twice. Mes­sages can be in­her­ently idem­potent (e.g., the mes­sages "All Wid­gets on Sale until May 30" or "Get me a quote for XYZ wid­gets" are un­likely to do harm if they are re­ceived twice), or the re­ceiv­ing com­pon­ent can be made idem­potent by in­sert­ing a spe­cial Mes­sage Filter that elim­in­ates du­plic­ate mes­sages. Idem­po­tence is very handy be­cause it allows us to simply resend mes­sages when we are in doubt whether the re­cip­i­ent has re­ceived it. The TCP/IP pro­tocol uses a sim­ilar mech­an­ism to ensure re­li­able mes­sage de­liv­ery without un­ne­ces­sary over­head (see [Stevens]).

                                                                                                                                                          动态收件人列表

                                                                                                                                                          Dy­namic Re­cip­i­ent List

                                                                                                                                                          尽管收件人列表的目的是保持控制,但让收件人自己配置存储在收件人列表中的规则可能会很有用,例如,如果收件人想要根据无法轻松表示的规则订阅特定消息,发布-订阅频道主题的形式。我们在消息过滤器下提到了这些类型的订阅规则模式例如“如果价格低于 48.31 美元,则接受消息。” 为了最大限度地减少网络流量,我们仍然希望仅将消息发送给感兴趣的各方,而不是广播消息并让每个接收者决定是否处理消息。为了实现此功能,接收者可以通过特殊的控制通道将其订阅首选项发送到接收者列表。收件人列表将首选项存储在规则库中,并使用它来编译每条消息的收件人列表。这种方法使订阅者可以控制消息过滤,但可以利用收件人列表的效率来分发消息。该解决方案将动态路由器的属性与收件人列表相结合,以创建动态收件人列表(见图)。

                                                                                                                                                          Even though the intent of the Re­cip­i­ent List is to main­tain con­trol, it can be useful to let the re­cip­i­ents them­selves con­fig­ure the rules stored in the Re­cip­i­ent Listfor ex­ample, if re­cip­i­ents want to sub­scribe to spe­cific mes­sages based on rules that cannot easily be rep­res­en­ted in the form of Pub­lish-Sub­scribe Chan­nel topics. We men­tioned these types of sub­scrip­tion rules under the Mes­sage Filter pat­tern­for ex­ample "accept the mes­sage if the price is less than $48.31." To min­im­ize net­work traffic, we would still want to send the mes­sages only to in­ter­ested parties as op­posed to broad­cast­ing it and let­ting each re­cip­i­ent decide whether or not to pro­cess the mes­sage. To im­ple­ment this func­tion­al­ity, re­cip­i­ents can send their sub­scrip­tion pref­er­ences to the Re­cip­i­ent List via a spe­cial con­trol chan­nel. The Re­cip­i­ent List stores the pref­er­ences in a rules base and uses it to com­pile the re­cip­i­ent list for each mes­sage. This ap­proach gives the sub­scribers con­trol over the mes­sage fil­ter­ing but lever­ages the ef­fi­ciency of the Re­cip­i­ent List to dis­trib­ute the mes­sages. This solu­tion com­bines the prop­er­ties of a Dy­namic Router with a Re­cip­i­ent List to create a dy­namic Re­cip­i­ent List (see figure).

                                                                                                                                                          动态收件人列表由收件人通过控制通道配置

                                                                                                                                                          A Dy­namic Re­cip­i­ent List Is Con­figured by the Re­cip­i­ents via a Con­trol Chan­nel

                                                                                                                                                          图形/07inf10.gif

                                                                                                                                                          这种方法对于消息过滤器模式中讨论的价格更新示例非常有效。由于它将控制权分配给各个接收者,因此它不适合此模式开头提到的报价示例,因为我们希望控制参与投标的供应商。

                                                                                                                                                          This ap­proach would work well for the price update ex­ample dis­cussed in the Mes­sage Filter pat­tern. Since it as­signs con­trol to the in­di­vidual re­cip­i­ents, it is not suit­able for the price quote ex­ample men­tioned at the be­gin­ning of this pat­tern, though, be­cause we want to con­trol the vendors that get to par­ti­cip­ate in the bid.

                                                                                                                                                          网络(输入)效率

                                                                                                                                                          Net­work (In)Ef­fi­cien­cies

                                                                                                                                                          将一条消息发送给所有可能的收件人(然后由其过滤该消息)或将单独的消息发送给每个收件人是否更有效,这在很大程度上取决于消息传递基础设施的实现。一般来说,我们可以假设消息的收件人越多,它引起的网络流量就越多。不过,也有例外。一些发布-订阅消息传递系统基于 IP 多播功能,可以通过单次网络传输将消息路由到多个收件人(仅需要对丢失的消息进行重传)。IP 多播利用以太网的总线架构。当 IP 数据包通过网络发送时,同一以太网段上的所有网络适配器 (NIC) 都会接收该数据包。通常情况下,NIC 验证数据包的预期接收者,如果数据包未发送至与 NIC 关联的 IP 地址,则忽略它。多播路由允许属于指定多播组的所有接收者从总线上读取数据包。这导致单个数据包能够被多个 NIC 接收,然后这些 NIC 将数据传递到与网络连接关联的相应应用程序。由于以太网总线架构,这种方法在本地网络上非常有效。它无法在需要点对点 TCP/IP 连接的 Internet 上工作。一般来说,我们可以说接收者之间的距离越远,使用电子邮件的效率就越高。多播路由允许属于指定多播组的所有接收者从总线上读取数据包。这导致单个数据包能够被多个 NIC 接收,然后这些 NIC 将数据传递到与网络连接关联的相应应用程序。由于以太网总线架构,这种方法在本地网络上非常有效。它无法在需要点对点 TCP/IP 连接的 Internet 上工作。一般来说,我们可以说接收者之间的距离越远,使用电子邮件的效率就越高。多播路由允许属于指定多播组的所有接收者从总线上读取数据包。这导致单个数据包能够被多个 NIC 接收,然后这些 NIC 将数据传递到与网络连接关联的相应应用程序。由于以太网总线架构,这种方法在本地网络上非常有效。它无法在需要点对点 TCP/IP 连接的 Internet 上工作。一般来说,我们可以说接收者之间的距离越远,使用电子邮件的效率就越高。这导致单个数据包能够被多个 NIC 接收,然后这些 NIC 将数据传递到与网络连接关联的相应应用程序。由于以太网总线架构,这种方法在本地网络上非常有效。它无法在需要点对点 TCP/IP 连接的 Internet 上工作。一般来说,我们可以说接收者之间的距离越远,使用电子邮件的效率就越高。这导致单个数据包能够被多个 NIC 接收,然后这些 NIC 将数据传递到与网络连接关联的相应应用程序。由于以太网总线架构,这种方法在本地网络上非常有效。它无法在需要点对点 TCP/IP 连接的 Internet 上工作。一般来说,我们可以说接收者之间的距离越远,使用电子邮件的效率就越高。收件人列表而不是发布-订阅通道

                                                                                                                                                          Whether it is more ef­fi­cient to send one mes­sage to all pos­sible re­cip­i­ents who then filter the mes­sage or to send in­di­vidual mes­sages to each re­cip­i­ent de­pends very much on the im­ple­ment­a­tion of the mes­saging in­fra­struc­ture. Gen­er­ally, we can assume that the more re­cip­i­ents a mes­sage has, the more net­work traffic it causes. How­ever, there are ex­cep­tions. Some pub­lish-sub­scribe mes­saging sys­tems are based on IP Mul­tic­ast func­tion­al­ity and can route mes­sages to mul­tiple re­cip­i­ents with a single net­work trans­mis­sion (re­quir­ing re­trans­mis­sion only for lost mes­sages). IP Mul­tic­ast takes ad­vant­age of Eth­er­net's bus ar­chi­tec­ture. When an IP packet is sent across the net­work, all net­work ad­apters (NIC) on the same Eth­er­net seg­ment re­ceive the packet. Nor­mally, the NIC veri­fies the in­ten­ded re­cip­i­ent of the packet and ig­nores it if the packet is not ad­dressed to the IP ad­dress the NIC is as­so­ci­ated with. Mul­tic­ast rout­ing allows all re­ceiv­ers that are part of a spe­cified mul­tic­ast group to read the packet off the bus. This res­ults in a single packet being able to be re­ceived by mul­tiple NICs who then pass the data to the re­spect­ive ap­plic­a­tion as­so­ci­ated with the net­work con­nec­tion. This ap­proach can be very ef­fi­cient on local net­works due to the Eth­er­net bus ar­chi­tec­ture. It does not work across the In­ter­net where point-to-point TCP/IP con­nec­tions are re­quired. In gen­eral, we can say that the fur­ther apart the re­cip­i­ents are, the more ef­fi­cient it is to use a Re­cip­i­ent List in­stead of a Pub­lish-Sub­scribe Chan­nel.

                                                                                                                                                          广播方法是否比收件人列表更有效不仅取决于网络基础设施,还取决于收件人总数与应处理消息的数量之间的比例。如果平均而言大多数收件人都在收件人列表中,则简单地广播消息并让(少数)非参与者过滤掉该消息可能会更有效。然而,如果平均而言,所有可能的收件人中只有一小部分对特定消息感兴趣,则几乎可以保证收件人列表会更有效。

                                                                                                                                                          Whether a broad­cast ap­proach is more ef­fi­cient than a Re­cip­i­ent List de­pends not only on the net­work in­fra­struc­ture, but also on the pro­por­tion between the total number of re­cip­i­ents and the number that are sup­posed to pro­cess the mes­sage. If on av­er­age most re­cip­i­ents are in the re­cip­i­ent list, it may be more ef­fi­cient to simply broad­cast the mes­sage and have the (few) non­par­ti­cipants filter the mes­sage out. If, how­ever, on av­er­age only a small por­tion of all pos­sible re­cip­i­ents are in­ter­ested in a par­tic­u­lar mes­sage, the Re­cip­i­ent List is almost guar­an­teed to be more ef­fi­cient.

                                                                                                                                                          收件人列表与发布-订阅和过滤器

                                                                                                                                                          Re­cip­i­ent List versus Pub­lish-Sub­scribe and Fil­ters

                                                                                                                                                          我们多次对比了使用带有收件人列表的预测路由和使用发布-订阅通道消息过滤器数组的反应式过滤来实现相同的功能一些决策标准与基于内容的路由器消息过滤器阵列之间的比较的标准相同。但是,如果是收件人列表,则邮件可以发送给多个收件人,从而使过滤器选项更具吸引力。

                                                                                                                                                          A number of times we have con­tras­ted im­ple­ment­ing the same func­tion­al­ity using pre­dict­ive rout­ing with a Re­cip­i­ent List and using re­act­ive fil­ter­ing using a Pub­lish-Sub­scribe Chan­nel and an array of Mes­sage Fil­ters. Some of the de­cision cri­teria are the same as those of the com­par­ison between the Con­tent-Based Router and the Mes­sage Filter array. How­ever, in case of a Re­cip­i­ent List, the mes­sage can travel to mul­tiple re­cip­i­ents, making the filter option more at­tract­ive.

                                                                                                                                                          收件人列表与消息过滤器数组

                                                                                                                                                          Re­cip­i­ent List versus Mes­sage Filter Array

                                                                                                                                                          图形/07inf11.gif

                                                                                                                                                          下表对两种解决方案进行了比较:

                                                                                                                                                          The fol­low­ing table com­pares the two solu­tions:

                                                                                                                                                          收件人名单

                                                                                                                                                          Re­cip­i­ent List

                                                                                                                                                          带有消息过滤器的发布-订阅通道

                                                                                                                                                          Pub­lish-Sub­scribe Chan­nel with Mes­sage Fil­ters

                                                                                                                                                          中央控制和维护预测路由。

                                                                                                                                                          Cent­ral con­trol and main­ten­an­ce­pre­dict­ive rout­ing.

                                                                                                                                                          分布式控制和维护无功滤波。

                                                                                                                                                          Dis­trib­uted con­trol and main­ten­an­cere­act­ive fil­ter­ing.

                                                                                                                                                          路由器需要了解参与者。如果添加或删除参与者,则可能需要更新路由器(除非使用动态路由器,但代价是失去控制)。

                                                                                                                                                          Router needs to know about par­ti­cipants. Router may need to be up­dated if par­ti­cipants are added or re­moved (unless using dy­namic router, but at ex­pense of losing con­trol).

                                                                                                                                                          无需参与者了解。添加或删除参与者很容易。

                                                                                                                                                          No know­ledge of par­ti­cipants re­quired. Adding or re­mov­ing par­ti­cipants is easy.

                                                                                                                                                          通常用于商业交易,例如请求报价。

                                                                                                                                                          Often used for busi­ness trans­ac­tions, e.g., re­quest for quote.

                                                                                                                                                          通常用于事件通知或信息性消息。

                                                                                                                                                          Often used for event no­ti­fic­a­tions or in­form­a­tional mes­sages.

                                                                                                                                                          如果仅限于基于队列的通道,通常会更有效。

                                                                                                                                                          Gen­er­ally more ef­fi­cient if lim­ited to queue-based chan­nels.

                                                                                                                                                          通过发布-订阅通道可以提高效率(取决于基础设施)。

                                                                                                                                                          Can be more ef­fi­cient with pub­lish-sub­scribe chan­nels (de­pends on in­fra­struc­ture).

                                                                                                                                                          如果我们向多个收件人发送消息,我们可能需要稍后协调结果。例如,如果我们向多个信用机构发送信用评分请求,我们应该等到所有结果返回,以便我们可以比较结果并准确计算信用评分中位数。对于其他不太重要的功能,我们可能只采用第一个可用的响应来优化消息吞吐量。这些类型的策略通常在聚合器内实现分散-聚集描述了我们从一条消息开始,将其发送给多个收件人,然后将响应重新组合回一条消息的情况。

                                                                                                                                                          If we send a mes­sage to mul­tiple re­cip­i­ents, we may need to re­con­cile the res­ults later. For ex­ample, if we send a re­quest for a credit score to mul­tiple credit agen­cies, we should wait until all res­ults come back so that we can com­pare the res­ults and ac­cur­ately com­pute the median credit score. With other, less crit­ical func­tions, we may just take the first avail­able re­sponse to op­tim­ize mes­sage through­put. These types of strategies are typ­ic­ally im­ple­men­ted inside an Ag­greg­ator. Scat­ter-Gather de­scribes situ­ations in which we start with a single mes­sage, send it to mul­tiple re­cip­i­ents, and re­com­bine the re­sponses back into a single mes­sage.

                                                                                                                                                          如果消息传递系统仅提供点对点通道但不提供发布-订阅通道,则动态收件人列表可用于实现发布-订阅通道。接收者列表将保留订阅该主题的所有点对点频道的列表。每个主题都可以由一个特定的收件人列表实例来表示。如果我们需要应用特殊标准以允许收件人订阅特定数据源,则此解决方案也很有用。虽然大多数发布-订阅通道允许任何组件订阅该通道,但接收者列表通过限制谁可以订阅列表,可以轻松实现逻辑来控制对源数据的访问。当然,这假设消息传递系统阻止接收者直接访问接收者列表的输入通道。

                                                                                                                                                          A dy­namic Re­cip­i­ent List can be used to im­ple­ment a Pub­lish-Sub­scribe Chan­nel if a mes­saging system provides only Point-to-Point Chan­nels but no Pub­lish-Sub­scribe Chan­nels. The Re­cip­i­ent List would keep a list of all Point-to-Point Chan­nels that are sub­scribed to the topic. Each topic can be rep­res­en­ted by one spe­cific Re­cip­i­ent List in­stance. This solu­tion can also be useful if we need to apply spe­cial cri­teria to allow re­cip­i­ents to sub­scribe to a spe­cific data source. While most Pub­lish-Sub­scribe Chan­nels allow any com­pon­ent to sub­scribe to the chan­nel, the Re­cip­i­ent List could easily im­ple­ment logic to con­trol access to the source data by lim­it­ing who gets to sub­scribe to the list. Of course, this as­sumes that the mes­saging system pre­vents the re­cip­i­ents from dir­ectly ac­cess­ing the input chan­nel into the Re­cip­i­ent List.

                                                                                                                                                          示例: 贷款经纪人

                                                                                                                                                          Ex­ample: Loan Broker

                                                                                                                                                          第 9 章“插曲:组合消息传递”中的组合消息传递示例使用收件人列表将贷款报价请求仅发送给合格的银行。插曲显示了Java、C# 和 TIBCO 中收件人列表的实现。

                                                                                                                                                          The com­posed mes­saging ex­ample in Chapter 9, "In­ter­lude: Com­posed Mes­saging," uses a Re­cip­i­ent List to route a loan quote re­quest only to qual­i­fied banks. The in­ter­lude shows im­ple­ment­a­tions of the Re­cip­i­ent List in Java, C#, and TIBCO.



                                                                                                                                                          示例: C# 和 MSMQ 中的动态收件人列表

                                                                                                                                                          Ex­ample: Dy­namic Re­cip­i­ent List in C# and MSMQ

                                                                                                                                                          此示例以动态路由器示例为基础,将其转换为动态收件人列表。代码结构非常相似。DynamicRecipientList侦听两个输入队列,一个用于传入消息 ( inQueue ),另一个用于控制队列 ( controlQueue),接收者可以在其中提交他们的订阅偏好。控制队列上的消息必须格式化为由冒号 (:) 分隔的两部分组成的字符串。第一部分是指示接收者订阅首选项的字符列表。收件人表示希望接收以指定字母之一开头的所有消息。控制消息的第二部分指定接收方侦听的队列的名称。例如,控制消息W:WidgetQueue告诉DynamicRecipientList 将所有以 W 开头的传入消息路由到队列 WidgetQueue 。 同样,消息WG:WidgetGadgetQueue指示DynamicRecipientList 将以 W 或 G 开头的消息到队列WidgetGadgetQueue

                                                                                                                                                          This ex­ample builds on the Dy­namic Router ex­ample to turn it into a dy­namic Re­cip­i­ent List. The code struc­ture is very sim­ilar. The Dy­namic­Re­cip­i­ent­L­ist listens on two input queues, one for in­com­ing mes­sages (in­Queue) and a con­trol queue (con­trolQueue) where re­cip­i­ents can hand in their sub­scrip­tion pref­er­ences. Mes­sages on the con­trol queue have to be format­ted as a string con­sist­ing of two parts sep­ar­ated by a colon (:). The first part is a list of char­ac­ters that in­dic­ate the sub­scrip­tion pref­er­ence of the re­cip­i­ent. The re­cip­i­ent ex­presses that it wants to re­ceive all mes­sages start­ing with one of the spe­cified let­ters. The second part of the con­trol mes­sage spe­cifies the name of the queue that the re­cip­i­ent listens on. For ex­ample, the con­trol mes­sage W:Wid­getQueue tells the Dy­namic­Re­cip­i­ent­L­ist to route all in­com­ing mes­sages that begin with W to the queue Wid­getQueue. Like­wise, the mes­sage WG:Wid­get­Gad­getQueue in­structs the Dy­namic­Re­cip­i­ent­L­ist to route mes­sages that start with either W or G to the queue Wid­get­Gad­getQueue.

                                                                                                                                                          
                                                                                                                                                          动态收件人列表类
                                                                                                                                                          {
                                                                                                                                                              受保护的消息队列inQueue;
                                                                                                                                                              受保护的消息队列控制队列;
                                                                                                                                                          
                                                                                                                                                              受保护的 IDictionary 路由表 = (IDictionary)(new
                                                                                                                                                          图形/ccc.gif哈希表());
                                                                                                                                                          
                                                                                                                                                          
                                                                                                                                                              公共动态收件人列表(消息队列 inQueue,
                                                                                                                                                          图形/ccc.gif消息队列(控制队列)
                                                                                                                                                              {
                                                                                                                                                                  this.inQueue = inQueue;
                                                                                                                                                                  this.controlQueue = controlQueue;
                                                                                                                                                          
                                                                                                                                                                  inQueue.ReceiveCompleted += 新
                                                                                                                                                          图形/ccc.gifReceiveCompletedEventHandler(OnMessage);
                                                                                                                                                                  inQueue.BeginReceive();
                                                                                                                                                          
                                                                                                                                                                  controlQueue.ReceiveCompleted +=
                                                                                                                                                                      新的 ReceiveCompletedEventHandler(OnControlMessage);
                                                                                                                                                                  controlQueue.BeginReceive();
                                                                                                                                                              }
                                                                                                                                                          
                                                                                                                                                              protected void OnMessage(对象源,
                                                                                                                                                          图形/ccc.gifReceiveCompletedEventArgs asyncResult)
                                                                                                                                                              {
                                                                                                                                                                  消息队列 mq = (消息队列)源;
                                                                                                                                                                  mq.Formatter = 新 System.Messaging.XmlMessageFormatter
                                                                                                                                                                                     (new String[] {"System.String,mscorlib"});
                                                                                                                                                                  消息消息 = mq.EndReceive(asyncResult.AsyncResult);
                                                                                                                                                          
                                                                                                                                                                  if (((String)message.Body).Length > 0)
                                                                                                                                                                  {
                                                                                                                                                                      字符键 = ((String)message.Body)[0];
                                                                                                                                                          
                                                                                                                                                                      ArrayList 目的地 = (ArrayList)routingTable[key];
                                                                                                                                                                      foreach(目的地中的消息队列目的地)
                                                                                                                                                                      {
                                                                                                                                                                          目的地.发送(消息);
                                                                                                                                                                          Console.WriteLine("发送消息" + message.Body +
                                                                                                                                                                                            " 到 " + 目的地.路径);
                                                                                                                                                                      }
                                                                                                                                                                  }
                                                                                                                                                                  mq.BeginReceive();
                                                                                                                                                              }
                                                                                                                                                          
                                                                                                                                                              // 控制消息格式为 XYZ:QueueName 作为单个字符串
                                                                                                                                                              protected void OnControlMessage(对象源,
                                                                                                                                                          图形/ccc.gifReceiveCompletedEventArgs asyncResult)
                                                                                                                                                              {
                                                                                                                                                                  消息队列 mq = (消息队列)源;
                                                                                                                                                                  mq.Formatter = 新 System.Messaging.XmlMessageFormatter
                                                                                                                                                                                     (new String[] {"System.String,mscorlib"});
                                                                                                                                                                  消息消息 = mq.EndReceive(asyncResult.AsyncResult);
                                                                                                                                                          
                                                                                                                                                                  字符串文本 = ((String)message.Body);
                                                                                                                                                                  String[] split = (text.Split(new char[] {':'}, 2));
                                                                                                                                                                  if (split.Length == 2)
                                                                                                                                                                  {
                                                                                                                                                                      char[] 键 = split[0].ToCharArray();
                                                                                                                                                                      String 队列名称 = split[1];
                                                                                                                                                                      MessageQueue 队列 = FindQueue(queueName);
                                                                                                                                                                      foreach(键中的字符 c)
                                                                                                                                                                      {
                                                                                                                                                                          if (!routingTable.Contains(c))
                                                                                                                                                                          {
                                                                                                                                                                              routingTable.Add(c, new ArrayList());
                                                                                                                                                                          }
                                                                                                                                                                          ((ArrayList)(routingTable[c])).Add(队列);
                                                                                                                                                                          Console.WriteLine("订阅队列" + 队列名称
                                                                                                                                                          图形/ccc.gif+“消息”+c);
                                                                                                                                                                      }
                                                                                                                                                                  }
                                                                                                                                                                  mq.BeginReceive();
                                                                                                                                                              }
                                                                                                                                                          
                                                                                                                                                              protected MessageQueue FindQueue(字符串队列名称)
                                                                                                                                                              {
                                                                                                                                                                  if (!MessageQueue.Exists(queueName))
                                                                                                                                                                  {
                                                                                                                                                                      返回 MessageQueue.Create(queueName);
                                                                                                                                                                  }
                                                                                                                                                                  别的
                                                                                                                                                                      返回新的消息队列(队列名称);
                                                                                                                                                              }
                                                                                                                                                          }
                                                                                                                                                          
                                                                                                                                                          
                                                                                                                                                          class Dy­namic­Re­cip­i­ent­L­ist
                                                                                                                                                          {
                                                                                                                                                              pro­tec­ted Mes­sageQueue in­Queue;
                                                                                                                                                              pro­tec­ted Mes­sageQueue con­trolQueue;
                                                                                                                                                          
                                                                                                                                                              pro­tec­ted IDic­tion­ary rout­ingT­able = (IDic­tion­ary)(new
                                                                                                                                                           Hasht­able());
                                                                                                                                                          
                                                                                                                                                          
                                                                                                                                                              public Dy­namic­Re­cip­i­ent­L­ist(Mes­sageQueue in­Queue,
                                                                                                                                                           Mes­sageQueue con­trolQueue)
                                                                                                                                                              {
                                                                                                                                                                  this.in­Queue = in­Queue;
                                                                                                                                                                  this.con­trolQueue = con­trolQueue;
                                                                                                                                                          
                                                                                                                                                                  in­Queue.Re­ceive­Com­pleted += new
                                                                                                                                                           Re­ceive­Com­plete­dE­ventHand­ler(On­Mes­sage);
                                                                                                                                                                  in­Queue.Be­gin­Re­ceive();
                                                                                                                                                          
                                                                                                                                                                  con­trolQueue.Re­ceive­Com­pleted +=
                                                                                                                                                                      new Re­ceive­Com­plete­dE­ventHand­ler(On­Con­trolMes­sage);
                                                                                                                                                                  con­trolQueue.Be­gin­Re­ceive();
                                                                                                                                                              }
                                                                                                                                                          
                                                                                                                                                              pro­tec­ted void On­Mes­sage(Object source,
                                                                                                                                                           Re­ceive­Com­plete­dEvent­Args asyn­cRes­ult)
                                                                                                                                                              {
                                                                                                                                                                  Mes­sageQueue mq = (Mes­sageQueue)source;
                                                                                                                                                                  mq.Format­ter = new System.Mes­saging.Xm­lMes­sage­Format­ter
                                                                                                                                                                                     (new String[] {"System.String,mscorlib"});
                                                                                                                                                                  Mes­sage mes­sage = mq.En­dRe­ceive(asyn­cRes­ult.Asyn­cRes­ult);
                                                                                                                                                          
                                                                                                                                                                  if (((String)mes­sage.Body).Length > 0)
                                                                                                                                                                  {
                                                                                                                                                                      char key = ((String)mes­sage.Body)[0];
                                                                                                                                                          
                                                                                                                                                                      Ar­rayL­ist des­tin­a­tions  = (Ar­rayL­ist)rout­ingT­able[key];
                                                                                                                                                                      foreach (Mes­sageQueue des­tin­a­tion in des­tin­a­tions)
                                                                                                                                                                      {
                                                                                                                                                                          des­tin­a­tion.Send(mes­sage);
                                                                                                                                                                          Con­sole.WriteLine("send­ing mes­sage " + mes­sage.Body +
                                                                                                                                                                                            " to " + des­tin­a­tion.Path);
                                                                                                                                                                      }
                                                                                                                                                                  }
                                                                                                                                                                  mq.Be­gin­Re­ceive();
                                                                                                                                                              }
                                                                                                                                                          
                                                                                                                                                              // con­trol mes­sage format is XYZ:QueueName as a single string
                                                                                                                                                              pro­tec­ted void On­Con­trolMes­sage(Object source,
                                                                                                                                                           Re­ceive­Com­plete­dEvent­Args asyn­cRes­ult)
                                                                                                                                                              {
                                                                                                                                                                  Mes­sageQueue mq = (Mes­sageQueue)source;
                                                                                                                                                                  mq.Format­ter = new System.Mes­saging.Xm­lMes­sage­Format­ter
                                                                                                                                                                                     (new String[] {"System.String,mscorlib"});
                                                                                                                                                                  Mes­sage mes­sage = mq.En­dRe­ceive(asyn­cRes­ult.Asyn­cRes­ult);
                                                                                                                                                          
                                                                                                                                                                  String text = ((String)mes­sage.Body);
                                                                                                                                                                  String [] split = (text.Split(new char[] {':'}, 2));
                                                                                                                                                                  if (split.Length == 2)
                                                                                                                                                                  {
                                                                                                                                                                      char[] keys = split[0].ToCharAr­ray();
                                                                                                                                                                      String queueName = split[1];
                                                                                                                                                                      Mes­sageQueue queue = Find­Queue(queueName);
                                                                                                                                                                      foreach (char c in keys)
                                                                                                                                                                      {
                                                                                                                                                                          if (!rout­ingT­able.Con­tains(c))
                                                                                                                                                                          {
                                                                                                                                                                              rout­ingT­able.Add(c, new Ar­rayL­ist());
                                                                                                                                                                          }
                                                                                                                                                                          ((Ar­rayL­ist)(rout­ingT­able[c])).Add(queue);
                                                                                                                                                                          Con­sole.WriteLine("Sub­scribed queue " + queueName
                                                                                                                                                           + " for mes­sage " + c);
                                                                                                                                                                      }
                                                                                                                                                                  }
                                                                                                                                                                  mq.Be­gin­Re­ceive();
                                                                                                                                                              }
                                                                                                                                                          
                                                                                                                                                              pro­tec­ted Mes­sageQueue Find­Queue(string queueName)
                                                                                                                                                              {
                                                                                                                                                                  if (!Mes­sageQueue.Exists(queueName))
                                                                                                                                                                  {
                                                                                                                                                                      return Mes­sageQueue.Create(queueName);
                                                                                                                                                                  }
                                                                                                                                                                  else
                                                                                                                                                                      return new Mes­sageQueue(queueName);
                                                                                                                                                              }
                                                                                                                                                          }
                                                                                                                                                          

                                                                                                                                                          DynamicRecipientList使用更聪明(读起来复杂)的方式来存储收件人的首选项。为了优化传入消息的处理,DynamicRecipientList维护一个以传入消息的第一个字母为键的哈希表。动态路由器示例不同,哈希表不包含单个目标,而是包含所有订阅目标的 ArrayList。当DynamicRecipientList 收到消息时,它从哈希表中找到正确的目标列表然后迭代列表以向每个目的地发送一条消息。

                                                                                                                                                          The Dy­namic­Re­cip­i­ent­L­ist uses a bit more clever (read com­plic­ated) way to store the re­cip­i­ent's pref­er­ences. To op­tim­ize pro­cess­ing of in­com­ing mes­sages, the Dy­namic­Re­cip­i­ent­L­ist main­tains a Hasht­able keyed by the first letter of in­com­ing mes­sages. Unlike in the Dy­namic Router ex­ample, the Hasht­able con­tains not a single des­tin­a­tion, but an Ar­rayL­ist of all sub­scribed des­tin­a­tions. When the Dy­namic­Re­cip­i­ent­L­ist re­ceives a mes­sage, it loc­ates the cor­rect des­tin­a­tion list from the Hasht­able and then it­er­ates over the list to send one mes­sage to each des­tin­a­tion.

                                                                                                                                                          此示例不使用dunnoChannel(请参阅基于动态内容路由器)来处理与任何条件不匹配的传入消息。通常,如果邮件的收件人数为零,则收件人列表不会将其视为错误。

                                                                                                                                                          This ex­ample does not use a dun­noChan­nel (see Con­tent-Based Router or Dy­namic Router ) for in­com­ing mes­sages that do not match any cri­teria. Typ­ic­ally, a Re­cip­i­ent List does not con­sider it an error if there are zero re­cip­i­ents for a mes­sage.

                                                                                                                                                          此实现不允许收件人取消订阅。它也不会检测重复订阅。例如,如果收件人订阅同一消息类型两次,它将收到重复的消息。这与典型的发布-订阅语义不同,在典型的发布-订阅语义中,特定接收者只能订阅一个频道一次。如果需要,可以轻松更改以禁止重复订阅。

                                                                                                                                                          This im­ple­ment­a­tion does not allow re­cip­i­ents to un­sub­scribe. It also does not detect du­plic­ate sub­scrip­tions. For ex­ample, if a re­cip­i­ent sub­scribes twice for the same mes­sage type, it will re­ceive du­plic­ate mes­sages. This is dif­fer­ent from the typ­ical pub­lish-sub­scribe se­mantics where a spe­cific re­cip­i­ent can sub­scribe to one chan­nel only once. The Dy­namic­Re­cip­i­ent­L­ist could easily be changed to dis­al­low du­plic­ate sub­scrip­tions if that is de­sired.



                                                                                                                                                            分路器

                                                                                                                                                            Splitter

                                                                                                                                                            图形/splitter_icon.gif

                                                                                                                                                            通过集成解决方案传递的许多消息由多个元素组成。例如,客户下的订单通常不仅仅包含单个行项目。正如基于内容的路由器的描述中所述,每个行项目可能需要由不同的库存系统处理。因此,我们需要找到一种方法来处理完整的订单,但单独处理订单中包含的每个订单项。

                                                                                                                                                            Many mes­sages passing through an in­teg­ra­tion solu­tion con­sist of mul­tiple ele­ments. For ex­ample, an order placed by a cus­tomer typ­ic­ally con­sists of more than just a single line item. As out­lined in the de­scrip­tion of the Con­tent-Based Router, each line item may need to be handled by a dif­fer­ent in­vent­ory system. Thus, we need to find an ap­proach to pro­cess a com­plete order but treat each order item con­tained in the order in­di­vidu­ally.

                                                                                                                                                            如果消息包含多个元素,并且每个元素可能必须以不同的方式处理,我们如何处理它?

                                                                                                                                                            How can we pro­cess a mes­sage if it con­tains mul­tiple ele­ments, each of which may have to be pro­cessed in a dif­fer­ent way?



                                                                                                                                                            此路由问题的解决方案应该足够通用,能够处理不同数量和类型的元素。例如,订单可以包含任意数量的商品,因此我们不想创建一个假设商品数量固定的解决方案。我们也不想对消息包含的项目类型做出太多假设。例如,如果 Widgets & Gadgets 'R Us 明天开始销售书籍,我们希望尽量减少对整体解决方案的影响。

                                                                                                                                                            The solu­tion to this rout­ing prob­lem should be gen­eric enough that it can deal with vary­ing num­bers and types of ele­ments. For ex­ample, an order can con­tain any number of items, so we would not want to create a solu­tion that as­sumes a fixed number of items. Nor would we want to make too many as­sump­tions about what type of items the mes­sage con­tains. For ex­ample, if Wid­gets & Gad­gets 'R Us starts selling books to­mor­row, we want to min­im­ize the impact on the over­all solu­tion.

                                                                                                                                                            我们还希望保持对订单项目的控制,避免重复或丢失处理。例如,我们可以使用发布-订阅通道将完整的订单发送到每个订单管理系统,并让它挑选出它可以处理的项目。这种方法具有与基于内容的路由器中描述的相同的缺点;很难避免个别物品的丢失或重复运输。

                                                                                                                                                            We also want to main­tain con­trol over the order items and avoid du­plic­ated or lost pro­cess­ing. For ex­ample, we could send the com­plete order to each order man­age­ment system using a Pub­lish-Sub­scribe Chan­nel and let it pick out the items that it can handle. This ap­proach has the same dis­ad­vant­ages de­scribed in the Con­tent-Based Router; it would be very dif­fi­cult to avoid losing or du­plic­at­ing ship­ments of in­di­vidual items.

                                                                                                                                                            该解决方案还应该有效地利用网络资源。将完整的订单消息发送到可能仅处理订单的一部分的每个系统可能会导致额外的消息流量,尤其是随着目的地数量的增加。

                                                                                                                                                            The solu­tion should also be ef­fi­cient in its use of net­work re­sources. Send­ing the com­plete order mes­sage to each system that may pro­cess only a por­tion of the order can cause ad­di­tional mes­sage traffic, es­pe­cially as the number of des­tin­a­tions in­creases.

                                                                                                                                                            为了避免多次发送完整的消息,我们可以将原始消息拆分为与库存系统一样多的消息。然后,每条消息将仅包含可由特定系统处理的行项目。这种方法类似于基于内容的路由器,只不过我们分割消息,然后路由各个消息。这种方法很有效,但将解决方案与有关特定项目类型和相关目的地的知识联系起来。如果我们想改变路由规则怎么办?我们现在必须更改这个更复杂的“项目路由器”组件。我们使用了管道和过滤器架构之前将处理分解为定义良好的、可组合的组件,而不是将多个功能集中在一起,因此我们也应该能够在这里利用这种架构。

                                                                                                                                                            To avoid send­ing the com­plete mes­sage mul­tiple times, we could split the ori­ginal mes­sage into as many mes­sages as there are in­vent­ory sys­tems. Each mes­sage would then con­tain only the line items that can be handled by the spe­cific system. This ap­proach is sim­ilar to a Con­tent-Based Router except we are split­ting the mes­sage and then rout­ing the in­di­vidual mes­sages. This ap­proach would be ef­fi­cient but ties the solu­tion to know­ledge about the spe­cific item types and as­so­ci­ated des­tin­a­tions. What if we want to change the rout­ing rules? We would now have to change this more com­plex "item router" com­pon­ent. We used the Pipes and Fil­ters ar­chi­tec­ture before to break out pro­cess­ing into well-defined, com­pos­able com­pon­ents as op­posed to lump­ing mul­tiple func­tions to­gether, so we should be able to take ad­vant­age of this ar­chi­tec­ture here as well.

                                                                                                                                                            使用拆分器将复合消息分解为一系列单独的消息,每条消息都包含与一个项目相关的数据。

                                                                                                                                                            Use a Split­ter to break out the com­pos­ite mes­sage into a series of in­di­vidual mes­sages, each con­tain­ing data re­lated to one item.

                                                                                                                                                            图形/07inf12.gif



                                                                                                                                                            拆分器为传入消息中的每个单个元素(或元素子集)发布一条消息。在许多情况下,我们希望在每条结果消息中重复一些常见元素。可能需要这些额外的元素来使生成的子消息自包含并启用每个子消息的无状态处理。它还允许稍后协调相关的子消息。例如,每个订单项消息应包含订单号的副本,以便我们可以正确地将订单项关联回订单以及所有关联实体,例如下订单的客户(见图)。

                                                                                                                                                            The Split­ter pub­lishes one mes­sage for each single ele­ment (or a subset of ele­ments) in the in­com­ing mes­sage. In many cases, we want to repeat some common ele­ments in each res­ult­ing mes­sage. These extra ele­ments may be re­quired to make the res­ult­ing child mes­sage self-con­tained and enable state­less pro­cess­ing of each child mes­sage. It also allows re­con­cili­ation of as­so­ci­ated child mes­sages later on. For ex­ample, each order item mes­sage should con­tain a copy of the order number so we can prop­erly as­so­ci­ate the order item back to the order and all as­so­ci­ated en­tit­ies such as the cus­tomer pla­cing the order (see figure).

                                                                                                                                                            将公共数据元素复制到每个子消息中

                                                                                                                                                            Copy­ing a Common Data Ele­ment into Each Child Mes­sage

                                                                                                                                                            图形/07inf13.gif

                                                                                                                                                            迭代分离器

                                                                                                                                                            It­er­at­ing Split­ters

                                                                                                                                                            如前所述,许多企业集成系统以树形结构存储消息数据。树结构的美妙之处在于它是递归的。一个节点下面的每个子节点都是另一个子树的根。这使我们能够提取消息树的各个部分,并将它们作为消息树进一步处理。如果我们使用消息树,则可以轻松地将 Splitter配置为迭代指定节点下的所有子节点,并为每个子节点发送一条消息。这样的Splitter实现将是完全通用的,因为它不对子元素的数量和类型做出任何假设。许多商业 EAI 工具在术语迭代器定序器。由于我们试图避免供应商词汇以减少混淆的可能性,因此我们将这种样式的Splitter称为迭代Splitter

                                                                                                                                                            As men­tioned earlier, many en­ter­prise in­teg­ra­tion sys­tems store mes­sage data in a tree struc­ture. The beauty of a tree struc­ture is that it is re­curs­ive. Each child node un­der­neath a node is the root of an­other sub­tree. This allows us to ex­tract pieces of a mes­sage tree and pro­cess them fur­ther as a mes­sage tree on their own. If we use mes­sage trees, the Split­ter can easily be con­figured to it­er­ate through all chil­dren under a spe­cified node and send one mes­sage for each child node. Such a Split­ter im­ple­ment­a­tion would be com­pletely gen­eric be­cause it does not make any as­sump­tions about the number and type of child ele­ments. Many com­mer­cial EAI tools provide this type of func­tion­al­ity under the term iter­ator or se­quen­cer. Since we are trying to avoid vendor vocab­u­lary to reduce po­ten­tial for con­fu­sion, we call this style of Split­ter an it­er­at­ing Split­ter.

                                                                                                                                                            静态分配器

                                                                                                                                                            Static Split­ters

                                                                                                                                                            使用分离器但不限于重复元素。可以将大消息分割成单独的消息以简化处理。例如,许多 B2B 信息交换标准指定了非常全面的消息格式。这些巨大的消息通常是委员会设计的结果,并且大部分消息可能很少被使用。在许多情况下,将这些大型消息分割成单独的消息是有帮助的,每个消息都集中在大型消息的特定部分。这使得后续转换的开发变得更加容易,并且还可以节省网络带宽,因为我们可以将较小的消息路由到那些仅处理大型消息的一部分的组件。生成的消息通常发布到不同的通道而不是同一通道,因为它们代表不同子类型的消息。在这种情况下,结果消息的数量通常是固定的,而更一般的Splitter假定项目数量可变。为了区分这种风格的Splitter,我们将其称为静态Splitter 。静态拆分器在功能上等同于使用广播频道,后跟一组内容过滤器。

                                                                                                                                                            Using a Split­ter is not lim­ited to re­peat­ing ele­ments, though. A large mes­sage may be split into in­di­vidual mes­sages to sim­plify pro­cess­ing. For ex­ample, a number of B2B in­form­a­tion-ex­change stand­ards spe­cify very com­pre­hens­ive mes­sage formats. These huge mes­sages are often a result of design-by-com­mit­tee, and large por­tions of the mes­sages may rarely be used. In many in­stances it is help­ful to split these mega-mes­sages into in­di­vidual mes­sages, each centered on a spe­cific por­tion of the large mes­sage. This makes sub­se­quent trans­form­a­tions much easier to de­velop and can also save net­work band­width, since we can route smal­ler mes­sages to those com­pon­ents that deal only with a por­tion of the mega-mes­sage. The res­ult­ing mes­sages are often pub­lished to dif­fer­ent chan­nels rather than to the same chan­nel be­cause they rep­res­ent mes­sages of dif­fer­ent sub­types. In this scen­ario, the number of res­ult­ing mes­sages is gen­er­ally fixed, whereas the more gen­eral Split­ter as­sumes a vari­able number of items. To dis­tin­guish this style of Split­ter, we call it static Split­ter. A static Split­ter is func­tion­ally equi­val­ent to using a broad­cast chan­nel fol­lowed by a set of Con­tent Fil­ters.

                                                                                                                                                            静态拆分器将大消息分解为固定数量的较小消息

                                                                                                                                                            A Static Split­ter Breaks Up a Large Mes­sage into a Fixed Number of Smal­ler Mes­sages

                                                                                                                                                            图形/07inf14.gif

                                                                                                                                                            有序或无序子消息

                                                                                                                                                            Ordered or Un­ordered Child Mes­sages

                                                                                                                                                            在某些情况下,为子消息配备序列号以提高消息可追溯性并简化聚合器的任务很有用。 此外,最好为每个消息配备对原始(组合)消息的引用,以便可以将各个消息的处理结果关联回原始消息。该引用充当相关标识符

                                                                                                                                                            In some cases it is useful to equip child mes­sages with se­quence num­bers to im­prove mes­sage trace­ab­il­ity and sim­plify the task of an Ag­greg­ator. Also, it is a good idea to equip each mes­sage with a ref­er­ence to the ori­ginal (com­bined) mes­sage so that pro­cess­ing res­ults from the in­di­vidual mes­sages can be cor­rel­ated back to the ori­ginal mes­sage. This ref­er­ence acts as a Cor­rel­a­tion Iden­ti­fier.

                                                                                                                                                            如果使用消息信封(请参阅信封包装器) ,则应为每个新消息提供自己的消息信封,以使其符合消息传递基础结构。例如,如果基础设施要求消息在消息头中携带时间戳,我们会将原始消息的时间戳传播到每个消息的头。

                                                                                                                                                            If mes­sage en­vel­opes are used (see En­vel­ope Wrap­per ), each new mes­sage should be sup­plied with its own mes­sage en­vel­ope to make it com­pli­ant with the mes­saging in­fra­struc­ture. For ex­ample, if the in­fra­struc­ture re­quires a mes­sage to carry a timestamp in the mes­sage header, we would propag­ate the timestamp of the ori­ginal mes­sage to each mes­sage's header.

                                                                                                                                                            示例: 在 C# 中拆分 XML 订单文档

                                                                                                                                                            Ex­ample: Split­ting an XML Order Doc­u­ment in C#

                                                                                                                                                            许多消息传递系统使用 XML 消息。例如,假设收到的订单如下所示:

                                                                                                                                                            Many mes­saging sys­tems use XML mes­sages. For ex­ample, let's assume an in­com­ing order looks as fol­lows:

                                                                                                                                                            <订单>
                                                                                                                                                                <日期>2002年7月18日</日期>
                                                                                                                                                                <订单号>3825968</订单号>
                                                                                                                                                                <顾客>
                                                                                                                                                                    <id>12345</id>
                                                                                                                                                                    <名字>乔·多伊</名字>
                                                                                                                                                                </客户>
                                                                                                                                                                <订单项目>
                                                                                                                                                                    <项目>
                                                                                                                                                                        <数量>3.0</数量>
                                                                                                                                                                        <项目编号>W1234</项目编号>
                                                                                                                                                                        <描述>小部件</描述>
                                                                                                                                                                    </项目>
                                                                                                                                                                    <项目>
                                                                                                                                                                        <数量>2.0</数量>
                                                                                                                                                                        <项目编号>G2345</项目编号>
                                                                                                                                                                        <描述>小工具</描述>
                                                                                                                                                                    </项目>
                                                                                                                                                                </订单项目>
                                                                                                                                                            </订单>
                                                                                                                                                            
                                                                                                                                                            <order>
                                                                                                                                                                <date>7/18/2002</date>
                                                                                                                                                                <or­dernum­ber>3825968</or­dernum­ber>
                                                                                                                                                                <cus­tomer>
                                                                                                                                                                    <id>12345</id>
                                                                                                                                                                    <name>Joe Doe</name>
                                                                                                                                                                </cus­tomer>
                                                                                                                                                                <or­der­items>
                                                                                                                                                                    <item>
                                                                                                                                                                        <quant­ity>3.0</quant­ity>
                                                                                                                                                                        <itemno>W1234</itemno>
                                                                                                                                                                        <de­scrip­tion>A Widget</de­scrip­tion>
                                                                                                                                                                    </item>
                                                                                                                                                                    <item>
                                                                                                                                                                        <quant­ity>2.0</quant­ity>
                                                                                                                                                                        <itemno>G2345</itemno>
                                                                                                                                                                        <de­scrip­tion>A Gadget</de­scrip­tion>
                                                                                                                                                                    </item>
                                                                                                                                                                </or­der­items>
                                                                                                                                                            </order>
                                                                                                                                                            

                                                                                                                                                            我们希望拆分器将订单拆分为单独的订单项目。对于示例文档,Splitter应生成以下两条消息:

                                                                                                                                                            We want the Split­ter to split the order into in­di­vidual order items. For the ex­ample doc­u­ment, the Split­ter should gen­er­ate the fol­low­ing two mes­sages:

                                                                                                                                                            <订单项目>
                                                                                                                                                                <日期>2002年7月18日</日期>
                                                                                                                                                                <订单号>3825968</订单号>
                                                                                                                                                                <客户ID>12345</客户ID>
                                                                                                                                                                <数量>3.0</数量>
                                                                                                                                                            
                                                                                                                                                                <项目编号>W1234</项目编号>
                                                                                                                                                                <描述>小部件</描述>
                                                                                                                                                            </订单项目>
                                                                                                                                                            
                                                                                                                                                            <订单项目>
                                                                                                                                                                <日期>2002年7月18日</日期>
                                                                                                                                                                <订单号>3825968</订单号>
                                                                                                                                                                <客户ID>12345</客户ID>
                                                                                                                                                                <数量>2.0</数量>
                                                                                                                                                                <项目编号>G2345</项目编号>
                                                                                                                                                                <描述>小工具</描述>
                                                                                                                                                            </订单项目>
                                                                                                                                                            
                                                                                                                                                            <or­der­item>
                                                                                                                                                                <date>7/18/2002</date>
                                                                                                                                                                <or­dernum­ber>3825968</or­dernum­ber>
                                                                                                                                                                <cus­tom­erid>12345</cus­tom­erid>
                                                                                                                                                                <quant­ity>3.0</quant­ity>
                                                                                                                                                            
                                                                                                                                                                <itemno>W1234</itemno>
                                                                                                                                                                <de­scrip­tion>A Widget</de­scrip­tion>
                                                                                                                                                            </or­der­item>
                                                                                                                                                            
                                                                                                                                                            <or­der­item>
                                                                                                                                                                <date>7/18/2002</date>
                                                                                                                                                                <or­dernum­ber>3825968</or­dernum­ber>
                                                                                                                                                                <cus­tom­erid>12345</cus­tom­erid>
                                                                                                                                                                <quant­ity>2.0</quant­ity>
                                                                                                                                                                <itemno>G2345</itemno>
                                                                                                                                                                <de­scrip­tion>A Gadget</de­scrip­tion>
                                                                                                                                                            </or­der­item>
                                                                                                                                                            

                                                                                                                                                            每条订单项消息都使用原始订单消息中的订单日期、订单号和客户 ID 进行丰富。包含客户 ID 和订单日期使消息成为独立的,并使消息使用者无需存储各个消息的上下文。如果消息要由无状态服务器处理,这一点很重要。添加ordernumber字段对于以后重新聚合项目是必要的(请参阅聚合器) 。在此示例中,我们假设订单内商品的具体顺序与订单的完成无关,因此我们不必包含商品编号。

                                                                                                                                                            Each or­der­item mes­sage is being en­riched with the order date, the order number, and the cus­tomer ID from the ori­ginal order mes­sage. The in­clu­sion of the cus­tomer ID and the order date makes the mes­sage self-con­tained and keeps the mes­sage con­sumer from having to store con­text across in­di­vidual mes­sages. This is im­port­ant if the mes­sages are to be pro­cessed by state­less serv­ers. The ad­di­tion of the or­dernum­ber field is ne­ces­sary for later re­ag­greg­a­tion of the items (see Ag­greg­ator ). In this ex­ample we assume that the spe­cific se­quence of items inside an order is not rel­ev­ant for com­ple­tion of the order, so we did not have to in­clude an item number.

                                                                                                                                                            让我们看看C# 中的Splitter代码是什么样的。

                                                                                                                                                            Let's see what the Split­ter code looks like in C#.

                                                                                                                                                            
                                                                                                                                                            XMLSplitter 类
                                                                                                                                                            {
                                                                                                                                                                受保护的消息队列inQueue;
                                                                                                                                                                受保护的消息队列outQueue;
                                                                                                                                                            
                                                                                                                                                                公共 XMLSplitter(消息队列 inQueue, 消息队列 outQueue)
                                                                                                                                                                {
                                                                                                                                                                    this.inQueue = inQueue;
                                                                                                                                                                    this.outQueue = outQueue;
                                                                                                                                                            
                                                                                                                                                                    inQueue.ReceiveCompleted += 新
                                                                                                                                                            图形/ccc.gifReceiveCompletedEventHandler(OnMessage);
                                                                                                                                                                    inQueue.BeginReceive();
                                                                                                                                                            
                                                                                                                                                                    outQueue.Formatter = new ActiveXMessageFormatter();
                                                                                                                                                                }
                                                                                                                                                            
                                                                                                                                                                protected void OnMessage(对象源,
                                                                                                                                                            图形/ccc.gifReceiveCompletedEventArgs asyncResult)
                                                                                                                                                                {
                                                                                                                                                                    消息队列 mq = (消息队列)源;
                                                                                                                                                                    mq.Formatter = new ActiveXMessageFormatter();
                                                                                                                                                                    消息消息 = mq.EndReceive(asyncResult.AsyncResult);
                                                                                                                                                            
                                                                                                                                                                    XmlDocument doc = new XmlDocument();
                                                                                                                                                                    doc.LoadXml((String)message.Body);
                                                                                                                                                            
                                                                                                                                                                    XmlNodeList 节点列表;
                                                                                                                                                                    XmlElement root = doc.DocumentElement;
                                                                                                                                                            
                                                                                                                                                                    XmlNode 日期 = root.SelectSingleNode("日期");
                                                                                                                                                                    XmlNode ordernumber = root.SelectSingleNode("ordernumber");
                                                                                                                                                                    XmlNode id = root.SelectSingleNode("客户/id");
                                                                                                                                                                    XmlElement customerid = doc.CreateElement("customerid");
                                                                                                                                                                    customerid.InnerText = id.InnerXml;
                                                                                                                                                            
                                                                                                                                                                    nodeList = root.SelectNodes("/order/orderitems/item");
                                                                                                                                                            
                                                                                                                                                                    foreach(nodeList 中的 XmlNode 项)
                                                                                                                                                                    {
                                                                                                                                                                        XmlDocument orderItemDoc = new XmlDocument();
                                                                                                                                                                        orderItemDoc.LoadXml("<orderitem/>");
                                                                                                                                                                        XmlElement orderItem = orderItemDoc.DocumentElement;
                                                                                                                                                            
                                                                                                                                                                        orderItem.AppendChild(orderItemDoc.ImportNode(日期,
                                                                                                                                                            图形/ccc.gif真的));
                                                                                                                                                                        orderItem.AppendChild(orderItemDoc.ImportNode)
                                                                                                                                                            图形/ccc.gif(订单号,真实));
                                                                                                                                                                        orderItem.AppendChild(orderItemDoc.ImportNode)
                                                                                                                                                            图形/ccc.gif(客户ID,真实));
                                                                                                                                                            
                                                                                                                                                                        for (int i=0; i < item.ChildNodes.Count; i++)
                                                                                                                                                                        {
                                                                                                                                                                            orderItem.AppendChild(orderItemDoc.ImportNode)
                                                                                                                                                            图形/ccc.gif(item.ChildNodes[i], true));
                                                                                                                                                                        }
                                                                                                                                                            
                                                                                                                                                                        outQueue.Send(orderItem.OuterXml);
                                                                                                                                                                    }
                                                                                                                                                            
                                                                                                                                                                    mq.BeginReceive();
                                                                                                                                                                }
                                                                                                                                                            
                                                                                                                                                            }
                                                                                                                                                            
                                                                                                                                                            
                                                                                                                                                            class XMLSplit­ter
                                                                                                                                                            {
                                                                                                                                                                pro­tec­ted Mes­sageQueue in­Queue;
                                                                                                                                                                pro­tec­ted Mes­sageQueue out­Queue;
                                                                                                                                                            
                                                                                                                                                                public XMLSplit­ter(Mes­sageQueue in­Queue, Mes­sageQueue out­Queue)
                                                                                                                                                                {
                                                                                                                                                                    this.in­Queue = in­Queue;
                                                                                                                                                                    this.out­Queue = out­Queue;
                                                                                                                                                            
                                                                                                                                                                    in­Queue.Re­ceive­Com­pleted += new
                                                                                                                                                             Re­ceive­Com­plete­dE­ventHand­ler(On­Mes­sage);
                                                                                                                                                                    in­Queue.Be­gin­Re­ceive();
                                                                                                                                                            
                                                                                                                                                                    out­Queue.Format­ter = new Act­iveXMes­sage­Format­ter();
                                                                                                                                                                }
                                                                                                                                                            
                                                                                                                                                                pro­tec­ted void On­Mes­sage(Object source,
                                                                                                                                                             Re­ceive­Com­plete­dEvent­Args asyn­cRes­ult)
                                                                                                                                                                {
                                                                                                                                                                    Mes­sageQueue mq = (Mes­sageQueue)source;
                                                                                                                                                                    mq.Format­ter = new Act­iveXMes­sage­Format­ter();
                                                                                                                                                                    Mes­sage mes­sage = mq.En­dRe­ceive(asyn­cRes­ult.Asyn­cRes­ult);
                                                                                                                                                            
                                                                                                                                                                    Xm­l­Doc­u­ment doc = new Xm­l­Doc­u­ment();
                                                                                                                                                                    doc.LoadXml((String)mes­sage.Body);
                                                                                                                                                            
                                                                                                                                                                    Xm­l­NodeL­ist nodeL­ist;
                                                                                                                                                                    Xm­lEle­ment root = doc.Doc­u­men­tEle­ment;
                                                                                                                                                            
                                                                                                                                                                    Xm­l­Node date = root.Se­lectSingleN­ode("date");
                                                                                                                                                                    Xm­l­Node or­dernum­ber = root.Se­lectSingleN­ode("or­dernum­ber");
                                                                                                                                                                    Xm­l­Node id = root.Se­lectSingleN­ode("cus­tomer/id");
                                                                                                                                                                    Xm­lEle­ment cus­tom­erid = doc.Cre­ateEle­ment("cus­tom­erid");
                                                                                                                                                                    cus­tom­erid.In­ner­Text = id.In­ner­Xml;
                                                                                                                                                            
                                                                                                                                                                    nodeL­ist = root.Se­lect­Nodes("/order/or­der­items/item");
                                                                                                                                                            
                                                                                                                                                                    foreach (Xm­l­Node item in nodeL­ist)
                                                                                                                                                                    {
                                                                                                                                                                        Xm­l­Doc­u­ment or­der­Item­Doc = new Xm­l­Doc­u­ment();
                                                                                                                                                                        or­der­Item­Doc.LoadXml("<or­der­item/>");
                                                                                                                                                                        Xm­lEle­ment or­der­Item = or­der­Item­Doc.Doc­u­men­tEle­ment;
                                                                                                                                                            
                                                                                                                                                                        or­der­Item.Ap­pend­Child(or­der­Item­Doc.Im­port­Node(date,
                                                                                                                                                             true));
                                                                                                                                                                        or­der­Item.Ap­pend­Child(or­der­Item­Doc.Im­port­Node
                                                                                                                                                            (or­dernum­ber, true));
                                                                                                                                                                        or­der­Item.Ap­pend­Child(or­der­Item­Doc.Im­port­Node
                                                                                                                                                            (cus­tom­erid, true));
                                                                                                                                                            
                                                                                                                                                                        for (int i=0; i < item.Child­Nodes.Count; i++)
                                                                                                                                                                        {
                                                                                                                                                                            or­der­Item.Ap­pend­Child(or­der­Item­Doc.Im­port­Node
                                                                                                                                                            (item.Child­Nodes[i], true));
                                                                                                                                                                        }
                                                                                                                                                            
                                                                                                                                                                        out­Queue.Send(or­der­Item.Out­er­Xml);
                                                                                                                                                                    }
                                                                                                                                                            
                                                                                                                                                                    mq.Be­gin­Re­ceive();
                                                                                                                                                                }
                                                                                                                                                            
                                                                                                                                                            }
                                                                                                                                                            

                                                                                                                                                            大多数代码都集中在 XML 处理上。XMLSplitter使用与其他路由示例相同的事件驱动消费者结构。每个传入消息都会调用OnMessage方法,该方法将消息正文转换为 XML 文档以进行操作。首先,我们从订单文档中提取相关值。然后,我们迭代每个<item>子元素。我们通过指定 XPath 表达式/order/orderitems/item来完成此操作。简单的 XPath 表达式与文件路径非常相似,它沿着文档树向下延伸,与路径中指定的元素名称相匹配。对于每个<项目>我们组装一个新的 XML 文档,复制从订单和项目的子节点继承的字段。

                                                                                                                                                            Most of the code cen­ters on the XML pro­cess­ing. The XMLSplit­ter uses the same Event-Driven Con­sumer struc­ture as the other rout­ing ex­amples. Each in­com­ing mes­sage in­vokes the method On­Mes­sage, which con­verts the mes­sage body into an XML doc­u­ment for ma­nip­u­la­tion. First, we ex­tract the rel­ev­ant values from the order doc­u­ment. Then, we it­er­ate over each <item> child ele­ment. We do this by spe­cify­ing the XPath ex­pres­sion /order/or­der­items/item. A simple XPath ex­pres­sion is very sim­ilar to a file pathit des­cends down the doc­u­ment tree, match­ing the ele­ment names spe­cified in the path. For each <item> we as­semble a new XML doc­u­ment, copy­ing the fields car­ried over from the order and the item's child nodes.



                                                                                                                                                            示例: 使用 C# 和 XSL 拆分 XML 订单文档

                                                                                                                                                            Ex­ample: Split­ting an XML Order Doc­u­ment in C# and XSL

                                                                                                                                                            我们还可以创建一个 XSL 文档来将传入的 XML 转换为所需的格式,然后从转换后的 XML 文档创建输出消息,而不是手动操作 XML 节点和元素。当文档格式可能发生变化时,这更易于维护。我们所要做的就是更改 XSL 转换,而不对 C# 代码进行任何更改。

                                                                                                                                                            In­stead of ma­nip­u­lat­ing XML nodes and ele­ments manu­ally, we can also create an XSL doc­u­ment to trans­form the in­com­ing XML into the de­sired format and then create output mes­sages from the trans­formed XML doc­u­ment. That is more main­tain­able when the doc­u­ment format is likely to change. All we have to do is change the XSL trans­form­a­tion without any changes to the C# code.

                                                                                                                                                            新代码使用XslTransform类提供的Transform方法将输入文档转换为中间文档格式。中间文档格式对于每条结果消息都有一个子元素 orderitem 。该代码只是遍历所有子元素并为每个元素发布一条消息。

                                                                                                                                                            The new code uses the Trans­form method provided by the XslTrans­form class to con­vert the input doc­u­ment into an in­ter­me­di­ate doc­u­ment format. The in­ter­me­di­ate doc­u­ment format has one child ele­ment, or­der­item, for each res­ult­ing mes­sage. The code simply tra­verses all child ele­ments and pub­lishes one mes­sage for each ele­ment.

                                                                                                                                                            
                                                                                                                                                            XSLSplitter 类
                                                                                                                                                            {
                                                                                                                                                                受保护的消息队列inQueue;
                                                                                                                                                                受保护的消息队列outQueue;
                                                                                                                                                            
                                                                                                                                                                protected String styleSheet = "..\\..\\Order2OrderItem.xsl";
                                                                                                                                                                受保护的 XslTransform xslt;
                                                                                                                                                            
                                                                                                                                                                公共 XSLSplitter(消息队列 inQueue,消息队列 outQueue)
                                                                                                                                                                {
                                                                                                                                                                    this.inQueue = inQueue;
                                                                                                                                                                    this.outQueue = outQueue;
                                                                                                                                                            
                                                                                                                                                                    xslt = 新的 XslTransform();
                                                                                                                                                                    xslt.Load(styleSheet, null);
                                                                                                                                                            
                                                                                                                                                                    outQueue.Formatter = new ActiveXMessageFormatter();
                                                                                                                                                            
                                                                                                                                                                    inQueue.ReceiveCompleted += 新
                                                                                                                                                            图形/ccc.gifReceiveCompletedEventHandler(OnMessage);
                                                                                                                                                                    inQueue.BeginReceive();
                                                                                                                                                                }
                                                                                                                                                            
                                                                                                                                                                protected void OnMessage(对象源,
                                                                                                                                                            图形/ccc.gifReceiveCompletedEventArgs asyncResult)
                                                                                                                                                                {
                                                                                                                                                                    消息队列 mq = (消息队列)源;
                                                                                                                                                                    mq.Formatter = new ActiveXMessageFormatter();
                                                                                                                                                                    消息消息 = mq.EndReceive(asyncResult.AsyncResult);
                                                                                                                                                            
                                                                                                                                                                    尝试
                                                                                                                                                                    {
                                                                                                                                                                        XPathDocument doc = 新的 XPathDocument
                                                                                                                                                                                                (新的字符串读取器(
                                                                                                                                                            图形/ccc.gif(字符串)消息。正文));
                                                                                                                                                            
                                                                                                                                                                        XmlReader 阅读器 = xslt.Transform(doc, null, new
                                                                                                                                                            图形/ccc.gifXmlUrlResolver());
                                                                                                                                                            
                                                                                                                                                                        XmlDocument allItems = new XmlDocument();
                                                                                                                                                                        allItems.Load(阅读器);
                                                                                                                                                            
                                                                                                                                                                        XmlNodeList 节点列表 = allItems.DocumentElement。
                                                                                                                                                                                               GetElementsByTagName("orderitem");
                                                                                                                                                            
                                                                                                                                                                        foreach(nodeList 中的 XmlNode orderItem)
                                                                                                                                                                        {
                                                                                                                                                                            outQueue.Send(orderItem.OuterXml);
                                                                                                                                                            
                                                                                                                                                                        }
                                                                                                                                                                    }
                                                                                                                                                                    catch (Exception e) { Console.WriteLine(e.ToString()); } }
                                                                                                                                                                    mq.BeginReceive();
                                                                                                                                                                }
                                                                                                                                                            }
                                                                                                                                                            
                                                                                                                                                            
                                                                                                                                                            class XSLSplit­ter
                                                                                                                                                            {
                                                                                                                                                                pro­tec­ted Mes­sageQueue in­Queue;
                                                                                                                                                                pro­tec­ted Mes­sageQueue out­Queue;
                                                                                                                                                            
                                                                                                                                                                pro­tec­ted String styleSheet = "..\\..\\Or­der­2Or­der­Item.xsl";
                                                                                                                                                                pro­tec­ted XslTrans­form xslt;
                                                                                                                                                            
                                                                                                                                                                public XSLSplit­ter(Mes­sageQueue in­Queue, Mes­sageQueue out­Queue)
                                                                                                                                                                {
                                                                                                                                                                    this.in­Queue = in­Queue;
                                                                                                                                                                    this.out­Queue = out­Queue;
                                                                                                                                                            
                                                                                                                                                                    xslt = new XslTrans­form();
                                                                                                                                                                    xslt.Load(styleSheet, null);
                                                                                                                                                            
                                                                                                                                                                    out­Queue.Format­ter = new Act­iveXMes­sage­Format­ter();
                                                                                                                                                            
                                                                                                                                                                    in­Queue.Re­ceive­Com­pleted += new
                                                                                                                                                             Re­ceive­Com­plete­dE­ventHand­ler(On­Mes­sage);
                                                                                                                                                                    in­Queue.Be­gin­Re­ceive();
                                                                                                                                                                }
                                                                                                                                                            
                                                                                                                                                                pro­tec­ted void On­Mes­sage(Object source,
                                                                                                                                                             Re­ceive­Com­plete­dEvent­Args asyn­cRes­ult)
                                                                                                                                                                {
                                                                                                                                                                    Mes­sageQueue mq = (Mes­sageQueue)source;
                                                                                                                                                                    mq.Format­ter = new Act­iveXMes­sage­Format­ter();
                                                                                                                                                                    Mes­sage mes­sage = mq.En­dRe­ceive(asyn­cRes­ult.Asyn­cRes­ult);
                                                                                                                                                            
                                                                                                                                                                    try
                                                                                                                                                                    {
                                                                                                                                                                        XPath­Doc­u­ment doc = new XPath­Doc­u­ment
                                                                                                                                                                                                (new Strin­gReader(
                                                                                                                                                            (String)mes­sage.Body));
                                                                                                                                                            
                                                                                                                                                                        Xm­lReader reader = xslt.Trans­form(doc, null, new
                                                                                                                                                             Xm­lUrlRe­solver());
                                                                                                                                                            
                                                                                                                                                                        Xm­l­Doc­u­ment al­lItems = new Xm­l­Doc­u­ment();
                                                                                                                                                                        al­lItems.Load(reader);
                                                                                                                                                            
                                                                                                                                                                        Xm­l­NodeL­ist nodeL­ist = al­lItems.Doc­u­men­tEle­ment.
                                                                                                                                                                                               GetEle­ments­By­Tag­Name("or­der­item");
                                                                                                                                                            
                                                                                                                                                                        foreach (Xm­l­Node or­der­Item in nodeL­ist)
                                                                                                                                                                        {
                                                                                                                                                                            out­Queue.Send(or­der­Item.Out­er­Xml);
                                                                                                                                                            
                                                                                                                                                                        }
                                                                                                                                                                    }
                                                                                                                                                                    catch (Ex­cep­tion e) { Con­sole.WriteLine(e.To­String()); }
                                                                                                                                                                    mq.Be­gin­Re­ceive();
                                                                                                                                                                }
                                                                                                                                                            }
                                                                                                                                                            

                                                                                                                                                            我们从单独的文件中读取 XSL 文档,以便于编辑和测试。此外,它允许我们更改Splitter 的行为,而无需重新编译代码。

                                                                                                                                                            We read the XSL doc­u­ment from a sep­ar­ate file to make it easier to edit and test. Also, it allows us to change the be­ha­vior of the Split­ter without re­com­pil­ing the code.

                                                                                                                                                            
                                                                                                                                                            <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999
                                                                                                                                                            图形/ccc.gif/XSL/转换">
                                                                                                                                                                <xsl:输出方法=“xml”版本=“1.0”编码=“UTF-8”
                                                                                                                                                            图形/ccc.gif缩进=“是”/>
                                                                                                                                                            
                                                                                                                                                                <xsl:模板匹配="/order">
                                                                                                                                                                    <订单项目>
                                                                                                                                                                        <xsl:apply-templates select="orderitems/item"/>
                                                                                                                                                                    </订单项目>
                                                                                                                                                                </xsl:模板>
                                                                                                                                                            
                                                                                                                                                                <xsl:模板匹配=“项目”>
                                                                                                                                                                    <订单项目>
                                                                                                                                                                        <日期>
                                                                                                                                                                            <xsl:value-of select="parent::node()/parent::node
                                                                                                                                                            图形/ccc.gif()/日期"/>
                                                                                                                                                                        </日期>
                                                                                                                                                                        <订单号>
                                                                                                                                                                            <xsl:value-of select="parent::node()/parent::node
                                                                                                                                                            图形/ccc.gif()/订单号"/>
                                                                                                                                                                        </订单号>
                                                                                                                                                                        <客户ID>
                                                                                                                                                                            <xsl:value-of select="parent::node()/parent::node
                                                                                                                                                            图形/ccc.gif()/客户/id"/>
                                                                                                                                                                        </客户ID>
                                                                                                                                                                        <xsl:apply-templates select="*"/>
                                                                                                                                                                    </订单项目>
                                                                                                                                                                </xsl:模板>
                                                                                                                                                            
                                                                                                                                                                <xsl:模板匹配="*">
                                                                                                                                                                    <xsl:复制>
                                                                                                                                                                        <xsl:apply-templates select="@* | node()"/>
                                                                                                                                                                    </xsl:复制>
                                                                                                                                                                </xsl:模板>
                                                                                                                                                            
                                                                                                                                                            </xsl:样式表>
                                                                                                                                                            
                                                                                                                                                            
                                                                                                                                                            <xsl:stylesheet ver­sion="1.0" xmlns:xsl="http://www.w3.org/1999
                                                                                                                                                            /XSL/Trans­form">
                                                                                                                                                                <xsl:output method="xml" ver­sion="1.0" en­cod­ing="UTF-8"
                                                                                                                                                             indent="yes"/>
                                                                                                                                                            
                                                                                                                                                                <xsl:tem­plate match="/order">
                                                                                                                                                                    <or­der­items>
                                                                                                                                                                        <xsl:apply-tem­plates select="or­der­items/item"/>
                                                                                                                                                                    </or­der­items>
                                                                                                                                                                </xsl:tem­plate>
                                                                                                                                                            
                                                                                                                                                                <xsl:tem­plate match="item">
                                                                                                                                                                    <or­der­item>
                                                                                                                                                                        <date>
                                                                                                                                                                            <xsl:value-of select="parent::node()/parent::node
                                                                                                                                                            ()/date"/>
                                                                                                                                                                        </date>
                                                                                                                                                                        <or­dernum­ber>
                                                                                                                                                                            <xsl:value-of select="parent::node()/parent::node
                                                                                                                                                            ()/or­dernum­ber"/>
                                                                                                                                                                        </or­dernum­ber>
                                                                                                                                                                        <cus­tom­erid>
                                                                                                                                                                            <xsl:value-of select="parent::node()/parent::node
                                                                                                                                                            ()/cus­tomer/id"/>
                                                                                                                                                                        </cus­tom­erid>
                                                                                                                                                                        <xsl:apply-tem­plates select="*"/>
                                                                                                                                                                    </or­der­item>
                                                                                                                                                                </xsl:tem­plate>
                                                                                                                                                            
                                                                                                                                                                <xsl:tem­plate match="*">
                                                                                                                                                                    <xsl:copy>
                                                                                                                                                                        <xsl:apply-tem­plates select="@* | node()"/>
                                                                                                                                                                    </xsl:copy>
                                                                                                                                                                </xsl:tem­plate>
                                                                                                                                                            
                                                                                                                                                            </xsl:stylesheet>
                                                                                                                                                            

                                                                                                                                                            XSL 是一种声明性语言,因此它并不容易理解,除非您自己编写了相当多的 XSL(或者阅读了一本好的 XSL 书籍,例如 [Tennison] 。此 XSL 转换会查找任何出现的order元素(我们的文档中有一个)。一旦找到该元素,它就会为输出文档创建一个新的根元素(所有 XML 文档都必须有一个根元素),并继续处理输入文档的orderitems元素内的所有item元素。XSL 为找到的每个项目指定一个新的“模板”。该模板复制dateordernumbercustomerid元素order元素(即项目的父级的父级),然后附加该项目中的任何元素。生成的文档对于输入文档中的每个 item元素都有一个orderitem元素。这使得 C# 代码可以轻松地迭代元素并将它们作为消息发布。

                                                                                                                                                            XSL is a de­clar­at­ive lan­guage, so it is not easy to make sense of unless you have writ­ten a fair bit of XSL your­self (or read a good XSL book like [Ten­nison]). This XSL trans­form looks for any oc­cur­rence of the order ele­ment (there is one in our doc­u­ment). Once it finds this ele­ment, it cre­ates a new root ele­ment for the output doc­u­ment (all XML doc­u­ments have to have a single root ele­ment) and goes on to pro­cess all item ele­ments inside the or­der­items ele­ment of the input doc­u­ment. The XSL spe­cifies a new "tem­plate" for each item that is found. This tem­plate copies the date, or­dernum­ber, and cus­tom­erid ele­ments from the order ele­ment (which is the item's parent's parent) and then ap­pends any ele­ment from the item. The res­ult­ing doc­u­ment has one or­der­item ele­ment for each item ele­ment in the input doc­u­ment. This makes it easy for the C# code to it­er­ate over the ele­ments and pub­lish them as mes­sages.

                                                                                                                                                            我们很好奇这两个实现将如何执行。我们决定进行一次真正快速、非科学的性能测试。我们只是将 5,000 条订单消息通过管道传输到输入队列中,启动 Splitter 并测量 10,000 条商品消息到达输出队列所需的时间。我们使用本地消息队列在一台机器上的单个程序中执行这一切。我们测量使用 DOM 提取元素的XMLSplitter需要 7 秒,基于 XSL 的Splitter需要 5.3 秒。为了建立基线,一个虚拟处理器消耗输入队列的一条消息并在输出队列上发布同一消息两次,处理 5,000 条消息花费了不到 2 秒的时间。该时间包括虚拟处理器消耗 5,000 条消息并发布 10,000 条消息,以及测试工具消耗处理器发布的 10,000 条消息。因此,看起来 XSL 操作比“手动”移动元素更有效(如果我们减去基线,XSL 大约快 35%)。我们确信这两个程序都可以调整以获得最佳性能,但看到它们并行执行很有趣。

                                                                                                                                                            We were curi­ous about how the two im­ple­ment­a­tions would per­form. We de­cided to run a real quick, non­sci­entific per­form­ance test. We simply piped 5,000 order mes­sages into the input queue, star­ted the Split­ter, and meas­ured the time it took for 10,000 item mes­sages to arrive on the output queue. We ex­ecuted this all inside a single pro­gram on one ma­chine using local mes­sage queues. We meas­ured 7 seconds for the XMLSplit­ter that uses the DOM to ex­tract ele­ments and 5.3 seconds for the XSL-based Split­ter. To es­tab­lish a baseline, a dummy pro­cessor that con­sumes one mes­sage of the input queue and pub­lishes the same mes­sage twice on the output queue took just under 2 seconds for 5,000 mes­sages. This time in­cludes the dummy pro­cessor con­sum­ing 5,000 mes­sages and pub­lish­ing 10,000, and the test har­ness con­sum­ing the 10,000 mes­sages the pro­cessor pub­lished. So, it looks like the XSL ma­nip­u­la­tion is a little more ef­fi­cient than moving ele­ments around "by hand" (if we sub­tract the baseline, the XSL is about 35 per­cent faster). We are sure that either pro­gram could be tuned for max­imum per­form­ance, but it was in­ter­est­ing to see them ex­ecute side by side.



                                                                                                                                                              聚合器

                                                                                                                                                              Aggregator

                                                                                                                                                              图形/aggregator_icon.gif

                                                                                                                                                              拆分器可用于将单个消息分解为一系列可以单独处理的子消息。同样,收件人列表发布-订阅通道可用于将请求消息并行转发到多个收件人,以便获得多个响应以供选择。在大多数这些场景中,进一步处理取决于子消息的成功处理。例如,我们希望从多个供应商响应中选择最佳出价,或者我们希望在所有商品从仓库中取出后向客户收取订单费用。

                                                                                                                                                              A Split­ter is useful to break out a single mes­sage into a se­quence of submes­sages that can be pro­cessed in­di­vidu­ally. Like­wise, a Re­cip­i­ent List or a Pub­lish-Sub­scribe Chan­nel is useful to for­ward a re­quest mes­sage to mul­tiple re­cip­i­ents in par­al­lel in order to get mul­tiple re­sponses to choose from. In most of these scen­arios, the fur­ther pro­cess­ing de­pends on suc­cess­ful pro­cess­ing of the submes­sages. For ex­ample, we want to select the best bid from a number of vendor re­sponses or we want to bill the client for an order after all items have been pulled from the ware­house.

                                                                                                                                                              我们如何组合各个但相关的消息的结果,以便可以将它们作为一个整体进行处理?

                                                                                                                                                              How do we com­bine the res­ults of in­di­vidual but re­lated mes­sages so that they can be pro­cessed as a whole?



                                                                                                                                                              消息传递系统的异步特性使得跨多个消息收集信息变得具有挑战性。有多少条消息?如果我们向广播频道广播消息,我们可能不知道有多少收件人收听了该频道,因此无法知道预期会有多少响应。

                                                                                                                                                              The asyn­chron­ous nature of a mes­saging system makes col­lect­ing in­form­a­tion across mul­tiple mes­sages chal­len­ging. How many mes­sages are there? If we broad­cast a mes­sage to a broad­cast chan­nel, we may not know how many re­cip­i­ents listened to that chan­nel and there­fore cannot know how many re­sponses to expect.

                                                                                                                                                              即使我们使用Splitter ,响应消息也可能不会按照创建它们的顺序到达。由于各个消息可以通过不同的网络路径进行路由,因此消息传递基础设施通常可以保证每个消息的传递,但可能无法保证各个消息的传递顺序。此外,各个消息可能由不同的各方以不同的处理速度进行处理。因此,响应消息可能会无序传送(有关此问题的更详细说明,请参阅重新排序器)。

                                                                                                                                                              Even if we use a Split­ter, the re­sponse mes­sages may not arrive in the same se­quence in which they were cre­ated. As in­di­vidual mes­sages can be routed through dif­fer­ent net­work paths, the mes­saging in­fra­struc­ture can usu­ally guar­an­tee the de­liv­ery of each mes­sage but may not be able to guar­an­tee the order in which the in­di­vidual mes­sages are de­livered. In ad­di­tion, the in­di­vidual mes­sages may be pro­cessed by dif­fer­ent parties with dif­fer­ent pro­cess­ing speeds. As a result, re­sponse mes­sages may be de­livered out of order (see Resequen­cer for a more de­tailed de­scrip­tion of this prob­lem).

                                                                                                                                                              此外,大多数消息传递基础设施都以“最终有保证”的传递模式运行,这意味着消息可以保证传递到预期的接收者,但不能保证消息何时传递。我们应该等待消息多长时间?如果等待时间过长,可能会延迟后续处理。如果我们决定在不遗漏信息的情况下继续前进,我们就必须找到一种处理不完整信息的方法。即便如此,当丢失的消息(或多条消息)最终到达时我们该怎么办?在某些情况下,我们也许可以单独处理消息,但在其他情况下,这样做可能会导致重复处理。此外,如果我们忽略迟到的消息,我们将永久丢失其信息内容。

                                                                                                                                                              In ad­di­tion, most mes­saging in­fra­struc­tures op­er­ate in a "guar­an­teed, ul­ti­mately" de­liv­ery mode, which means that mes­sages are guar­an­teed to be de­livered to the in­ten­ded re­cip­i­ent, but there are no guar­an­tees as to when the mes­sages will be de­livered. How long should we wait for a mes­sage? If we wait too long, we may delay sub­se­quent pro­cess­ing. If we decide to move ahead without the miss­ing mes­sage, we have to find a way to work with in­com­plete in­form­a­tion. Even so, what should we do when the miss­ing mes­sage (or mes­sages) fi­nally ar­rives? In some cases, we may be able to pro­cess the mes­sage sep­ar­ately, but in other cases, doing so may lead to du­plic­ate pro­cess­ing. Also, if we ignore the late­comer mes­sages, we per­man­ently lose their in­form­a­tion con­tent.

                                                                                                                                                              所有这些问题都会使多个相关消息的组合处理变得复杂。如果一个单独的组件可以处理这些复杂性并将单个消息传递给取决于所有单独子消息是否存在的后续处理业务,那么实现业务逻辑就会容易得多。

                                                                                                                                                              All these issues can com­plic­ate the com­bined pro­cess­ing of mul­tiple but re­lated mes­sages. It would be much easier to im­ple­ment the busi­ness logic if a sep­ar­ate com­pon­ent could take care of these com­plex­it­ies and pass a single mes­sage to the sub­se­quent pro­cess­ing busi­ness that de­pends on the pres­ence of all in­di­vidual submes­sages.

                                                                                                                                                              使用有状态过滤器(聚合器)来收集和存储单个消息,直到收到一组完整的相关消息。然后,聚合器发布从各个消息中提取的单个消息。

                                                                                                                                                              Use a state­ful filter, an Ag­greg­ator, to col­lect and store in­di­vidual mes­sages until it re­ceives a com­plete set of re­lated mes­sages. Then, the Ag­greg­ator pub­lishes a single mes­sage dis­tilled from the in­di­vidual mes­sages.

                                                                                                                                                              图形/07inf15.gif



                                                                                                                                                              聚合器是一个特殊的过滤器(在管道和过滤器架构中),它接收消息流并识别相关的消息。一旦收到完整的消息集(稍后将详细介绍如何确定消息组何时完成),聚合器就会从每个相关消息中收集信息,并将单个聚合消息发布到输出通道以进行进一步处理。

                                                                                                                                                              The Ag­greg­ator is a spe­cial filter (in a Pipes and Fil­ters ar­chi­tec­ture) that re­ceives a stream of mes­sages and iden­ti­fies mes­sages that are cor­rel­ated. Once a com­plete set of mes­sages has been re­ceived (more later on how to decide when a set is com­plete), the Ag­greg­ator col­lects in­form­a­tion from each cor­rel­ated mes­sage and pub­lishes a single, ag­greg­ated mes­sage to the output chan­nel for fur­ther pro­cess­ing.

                                                                                                                                                              与之前的大多数路由模式不同,聚合器是一个有状态组件像基于内容的路由器这样的简单路由模式通常是无状态的,这意味着组件会一一处理传入的消息,并且不必在消息之间保留任何信息。处理消息后,此类组件的状态与消息到达之前的状态相同。因此,我们称这样的组件为无状态的。聚合不能是无状态的,因为它需要存储每个传入的消息,直到收到所有属于一起的消息。然后,它必须将与每条消息关联的信息提取到聚合消息中。聚合器不必完整存储每条传入消息。例如,如果我们正在处理传入的拍卖出价,我们可能只需要保留最高出价和关联的出价者 ID,而不是所有单个出价消息的历史记录。尽管如此,聚合器仍必须跨消息存储一些信息,因此是有状态的。

                                                                                                                                                              Unlike most of the pre­vi­ous rout­ing pat­terns, the Ag­greg­ator is a state­ful com­pon­ent. Simple rout­ing pat­terns like the Con­tent-Based Router are often state­less, which means the com­pon­ent pro­cesses in­com­ing mes­sages one by one and does not have to keep any in­form­a­tion between mes­sages. After pro­cess­ing a mes­sage, such a com­pon­ent is in the same state as it was before the mes­sage ar­rived. There­fore, we call such a com­pon­ent state­less. The Ag­greg­ator cannot be state­less, since it needs to store each in­com­ing mes­sage until all the mes­sages that belong to­gether have been re­ceived. Then, it must dis­till the in­form­a­tion as­so­ci­ated with each mes­sage into the ag­greg­ate mes­sage. The Ag­greg­ator does not ne­ces­sar­ily have to store each in­com­ing mes­sage in its en­tirety. For ex­ample, if we are pro­cess­ing in­com­ing auc­tion bids, we may need to keep only the highest bid and the as­so­ci­ated bidder ID, not the his­tory of all in­di­vidual bid mes­sages. Still, the Ag­greg­ator has to store some in­form­a­tion across mes­sages and is there­fore state­ful.

                                                                                                                                                              在设计Aggregator 时,我们需要指定以下属性:

                                                                                                                                                              When design­ing an Ag­greg­ator, we need to spe­cify the fol­low­ing prop­er­ties:

                                                                                                                                                              1. 相关性: 哪些传入消息属于同一组?

                                                                                                                                                              2. Cor­rel­a­tion: Which in­com­ing mes­sages belong to­gether?

                                                                                                                                                              3. 完整性条件: 我们什么时候准备好发布结果消息?

                                                                                                                                                              4. Com­plete­ness Con­di­tion: When are we ready to pub­lish the result mes­sage?

                                                                                                                                                              5. 聚合算法: 我们如何将接收到的消息组合成单个结果消息?

                                                                                                                                                              6. Ag­greg­a­tion Al­gorithm: How do we com­bine the re­ceived mes­sages into a single result mes­sage?

                                                                                                                                                              关联通常通过传入消息的类型或显式关联标识符来实现。第 272 页描述了完整性条件和聚合算法的常见选择。

                                                                                                                                                              Cor­rel­a­tion is typ­ic­ally achieved by either the type of the in­com­ing mes­sages or an ex­pli­cit Cor­rel­a­tion Iden­ti­fier. Common choices for the com­plete­ness con­di­tion and ag­greg­a­tion al­gorithm are de­scribed on page 272.

                                                                                                                                                              实施细节

                                                                                                                                                              Im­ple­ment­a­tion De­tails

                                                                                                                                                              由于消息系统的事件驱动性质,聚合器可以在任何时间以任何顺序接收相关消息。为了关联消息,聚合器维护活动聚合的列表,即聚合器已经接收到一些消息的聚合。当聚合器收到新消息时,它需要检查该消息是否是已存在聚合的一部分。如果不存在与此消息相关的聚合,则聚合器假定这是集合中的第一条消息并创建新的聚合。然后它将消息添加到新聚合中。如果聚合已经存在,则聚合器只需将消息添加到聚合中即可。

                                                                                                                                                              Due to the event-driven nature of a mes­saging system, the Ag­greg­ator may re­ceive re­lated mes­sages at any time and in any order. To as­so­ci­ate mes­sages, the Ag­greg­ator main­tains a list of active ag­greg­ates, that is, ag­greg­ates for which the Ag­greg­ator has re­ceived some mes­sages already. When the Ag­greg­ator re­ceives a new mes­sage, it needs to check whether the mes­sage is part of an already ex­ist­ing ag­greg­ate. If no ag­greg­ate re­lated to this mes­sage exists, the Ag­greg­ator as­sumes that this is the first mes­sage of a set and cre­ates a new ag­greg­ate. It then adds the mes­sage to the new ag­greg­ate. If an ag­greg­ate already exists, the Ag­greg­ator simply adds the mes­sage to the ag­greg­ate.

                                                                                                                                                              添加消息后,聚合器会评估受影响聚合的完整性条件。如果条件评估为 true,则根据聚合中累积的消息形成新的聚合消息并将其发布到输出通道。如果完整性条件评估为 false,则不会发布任何消息,并且聚合器会保持聚合处于活动状态以等待其他消息到达。

                                                                                                                                                              After adding the mes­sage, the Ag­greg­ator eval­u­ates the com­plete­ness con­di­tion for the af­fected ag­greg­ate. If the con­di­tion eval­u­ates to true, a new ag­greg­ate mes­sage is formed from the mes­sages ac­cu­mu­lated in the ag­greg­ate and pub­lished to the output chan­nel. If the com­plete­ness con­di­tion eval­u­ates to false, no mes­sage is pub­lished and the Ag­greg­ator keeps the ag­greg­ate active for ad­di­tional mes­sages to arrive.

                                                                                                                                                              下图说明了这一策略。在这个简单的场景中,传入消息通过 Correlation Identifier 关联。 相关标识符值为100 的第一条消息到达时,聚合器会初始化一个新聚合并将该消息存储在该聚合内。在我们的示例中,完整性条件指定至少三个消息,因此聚合尚未完成。当相关标识符值为100 的第二条消息到达时,聚合器将其添加到已存在的聚合中。同样,聚合尚未完成。第三条消息指定了不同的相关标识符101。因此,聚合器会针对该值启动新的聚合。第四消息涉及第一聚合(标识符100)。添加此消息后,聚合包含三个消息,因此现在满足完整性条件。结果,聚合器计算

                                                                                                                                                              The fol­low­ing dia­gram il­lus­trates this strategy. In this simple scen­ario, in­com­ing mes­sages are re­lated through a Cor­rel­a­tion Iden­ti­fier. When the first mes­sage with the Cor­rel­a­tion Iden­ti­fier value of 100 ar­rives, the Ag­greg­ator ini­tial­izes a new ag­greg­ate and stores the mes­sage inside that ag­greg­ate. In our ex­ample, the com­plete­ness con­di­tion spe­cifies a min­imum of three mes­sages, so the ag­greg­ate is not yet com­plete. When the second mes­sage with the Cor­rel­a­tion Iden­ti­fier value of 100 ar­rives, the Ag­greg­ator adds it to the already ex­ist­ing ag­greg­ate. Again, the ag­greg­ate is not yet com­plete. The third mes­sage spe­cifies a dif­fer­ent Cor­rel­a­tion Iden­ti­fier value, 101. As a result, the Ag­greg­ator starts a new ag­greg­ate for that value. The fourth mes­sage relates to the first ag­greg­ate (iden­ti­fier 100). After adding this mes­sage, the ag­greg­ate con­tains three mes­sages, so the com­plete­ness con­di­tion is now ful­filled. As a result, the Ag­greg­ator com­putes the ag­greg­ate mes­sage, marks the ag­greg­ate as com­pleted, and pub­lishes the result mes­sage.

                                                                                                                                                              聚合器内部结构

                                                                                                                                                              Ag­greg­ator In­tern­als

                                                                                                                                                              图形/07inf16.gif

                                                                                                                                                              每当聚合器收到无法与现有聚合关联的消息时,此策略就会创建一个新聚合。因此,聚合器不需要预先了解它可能产生的聚合。因此,我们将此变体称为自启动聚合器

                                                                                                                                                              This strategy cre­ates a new ag­greg­ate whenever the Ag­greg­ator re­ceives a mes­sage that cannot be as­so­ci­ated with an ex­ist­ing ag­greg­ate. There­fore, the Ag­greg­ator does not need prior know­ledge of the ag­greg­ates that it may pro­duce. Ac­cord­ingly, we call this vari­ant a self-start­ing Ag­greg­ator.

                                                                                                                                                              根据聚合策略,聚合器可能必须处理传入消息属于已关闭的聚合的情况,即传入消息在聚合消息发布后到达。为了避免启动新的聚合,聚合器必须保留一份已关闭的聚合列表。应定期清除此列表,以免其无限期增长。但是,我们需要小心,不要过早清除已关闭的聚合,因为这会导致任何消息延迟启动新的聚合。由于我们不需要存储完整的聚合,而只需存储它已关闭的事实,因此我们可以非常有效地存储关闭聚合的列表,并在清除算法中构建足够的安全裕度。我们还可以使用消息过期来忽略延迟时间过长的消息。

                                                                                                                                                              De­pend­ing on the ag­greg­a­tion strategy, the Ag­greg­ator may have to deal with the situ­ation that an in­com­ing mes­sage be­longs to an ag­greg­ate that has already been closed out­that is, the in­com­ing mes­sage ar­rives after the ag­greg­ate mes­sage has been pub­lished. In order to avoid start­ing a new ag­greg­ate, the Ag­greg­ator must keep a list of ag­greg­ates that have been closed out. This list should be purged peri­od­ic­ally so that it does not grow in­def­in­itely. How­ever, we need to be care­ful not to purge closed-out ag­greg­ates too soon be­cause that would cause any mes­sages that are delayed to start a new ag­greg­ate. Since we do not need to store the com­plete ag­greg­ate, but just the fact that it has been closed, we can store the list of closed ag­greg­ates quite ef­fi­ciently and build a suf­fi­cient safety margin into the purge al­gorithm. We can also use Mes­sage Ex­pir­a­tion to ignore mes­sages that have been delayed for an in­or­din­ate amount of time.

                                                                                                                                                              为了提高整体解决方案的稳健性,我们还可以允许聚合器侦听特定的控制通道,该通道允许手动清除所有活动聚合或特定聚合。如果我们想要从错误情况中恢复而无需重新启动聚合器组件,则此功能非常有用。同样,允许聚合器根据请求将活动聚合列表发布到特殊通道可能是一个非常有用的调试功能。这两个功能都是通常合并到控制总线中的功能类型的绝佳示例。

                                                                                                                                                              In order to in­crease the ro­bust­ness of the over­all solu­tion, we can also allow the Ag­greg­ator to listen on a spe­cific con­trol chan­nel that allows the manual pur­ging of all active ag­greg­ates or a spe­cific one. This fea­ture can be useful if we want to re­cover from an error con­di­tion without having to re­start the Ag­greg­ator com­pon­ent. Along the same lines, al­low­ing the Ag­greg­ator to pub­lish a list of active ag­greg­ates to a spe­cial chan­nel upon re­quest can be a very useful de­bug­ging fea­ture. Both func­tions are ex­cel­lent ex­amples of the kind of fea­tures typ­ic­ally in­cor­por­ated into a Con­trol Bus.

                                                                                                                                                              聚合策略

                                                                                                                                                              Ag­greg­a­tion Strategies

                                                                                                                                                              对于聚合器完整性条件有多种策略。可用的策略主要取决于我们是否知道预期有多少消息。聚合可以知道预期的子消息数量,因为它收到了原始复合消息的副本,或者因为每个单独的消息都包含总数(如拆分器示例中所述)。根据聚合器对消息流的了解程度,最常见的策略如下:

                                                                                                                                                              There are a number of strategies for ag­greg­ator com­plete­ness con­di­tions. The avail­able strategies primar­ily depend on whether or not we know how many mes­sages to expect. The Ag­greg­ator could know the number of submes­sages to expect be­cause it re­ceived a copy of the ori­ginal com­pos­ite mes­sage or be­cause each in­di­vidual mes­sage con­tains the total count (as de­scribed in the Split­ter ex­ample). De­pend­ing on how much the Ag­greg­ator knows about the mes­sage stream, the most common strategies are as fol­lows:

                                                                                                                                                              1. 等待全部: 等待收到所有响应。这种情况很可能出现在我们之前讨论的订单示例中。不完整的订单可能没有意义。因此,如果在某个超时期限内未收到所有项目,则聚合器应引发错误条件。这种方法可能为我们的决策提供最好的基础,但也可能是最慢、最脆弱的(另外,我们需要知道预期有多少消息)。单个丢失或延迟的消息将阻止整个聚合的进一步处理。在松散耦合的异步系统中,解决此类错误情况可能是一件复杂的事情,因为消息的异步流使得难以可靠地检测错误情况(在消息“丢失”之前我们应该等待多长时间?)。处理丢失消息的一种方法是重新请求消息。然而,这种方法要求聚合器知道消息的来源,这可能会在聚合器和其他组件之间引入额外的依赖关系。

                                                                                                                                                              2. Wait for All: Wait until all re­sponses are re­ceived. This scen­ario is most likely in the order ex­ample we dis­cussed earlier. An in­com­plete order may not be mean­ing­ful. So, if all items are not re­ceived within a cer­tain timeout period, an error con­di­tion should be raised by the Ag­greg­ator. This ap­proach may give us the best basis for de­cision making, but may also be the slow­est and most brittle (plus, we need to know how many mes­sages to expect). A single miss­ing or delayed mes­sage will pre­vent fur­ther pro­cess­ing of the whole ag­greg­ate. Resolv­ing such error con­di­tions can be a com­plic­ated matter in loosely coupled asyn­chron­ous sys­tems be­cause the asyn­chron­ous flow of mes­sages makes it hard to re­li­ably detect error con­di­tions (how long should we wait before a mes­sage is "miss­ing"?). One way to deal with miss­ing mes­sages is to re-re­quest the mes­sage. How­ever, this ap­proach re­quires the Ag­greg­ator to know the source of the mes­sage, which may in­tro­duce ad­di­tional de­pend­en­cies between the Ag­greg­ator and other com­pon­ents.

                                                                                                                                                              3. 超时: 等待指定时间长度的响应,然后通过评估在该时限内收到的响应来做出决定。如果没有收到响应,系统可能会报告异常或重试。如果对传入响应进行评分并且仅使用得分最高的消息(或少量消息),则此启发式方法非常有用。这种方式在“竞价”场景中很常见。

                                                                                                                                                              4. Timeout: Wait for a spe­cified length of time for re­sponses and then make a de­cision by eval­u­at­ing those re­sponses re­ceived within that time limit. If no re­sponses are re­ceived, the system may report an ex­cep­tion or retry. This heur­istic is useful if in­com­ing re­sponses are scored and only the mes­sage (or a small number of mes­sages) with the highest score is used. This ap­proach is common in "bid­ding" scen­arios.

                                                                                                                                                              5. 第一个最佳: 仅等到收到第一个(最快)响应并忽略所有其他响应。这种方法速度最快,但忽略了很多信息。这在响应时间至关重要的投标或报价场景中可能是实用的。

                                                                                                                                                              6. First Best: Wait only until the first (fast­est) re­sponse is re­ceived and ignore all other re­sponses. This ap­proach is the fast­est but ig­nores a lot of in­form­a­tion. It may be prac­tical in a bid­ding or quot­ing scen­ario where re­sponse time is crit­ical.

                                                                                                                                                              7. 超时覆盖: 等待指定的时间或直到收到具有预设最低分数的消息。在这种情况下,如果我们发现非常有利的回应,我们愿意提前中止;否则,我们将继续前进,直到时间到。如果此时没有找到明显的获胜者,则会对迄今为止收到的所有消息进行排名排序。

                                                                                                                                                              8. Timeout with Over­ride: Wait for a spe­cified amount of time or until a mes­sage with a preset min­imum score has been re­ceived. In this scen­ario, we are will­ing to abort early if we find a very fa­vor­able re­sponse; oth­er­wise, we keep going until time is up. If no clear winner was found at that point, rank or­der­ing among all the mes­sages re­ceived so far occurs.

                                                                                                                                                              9. 外部事件: 有时,聚合会因外部业务事件的到达而结束。例如,在金融行业中,交易日结束可能标志着传入报价汇总的结束。对此类事件使用固定计时器会降低灵活性,因为它不提供太多的可变性。此外,事件消息形式的指定业务事件允许对系统进行集中控制。聚合器可以在特殊控制通道上侦听事件消息或接收指示聚合结束的特殊格式的消息。

                                                                                                                                                              10. Ex­ternal Event: Some­times the ag­greg­a­tion is con­cluded by the ar­rival of an ex­ternal busi­ness event. For ex­ample, in the fin­an­cial in­dustry, the end of the trad­ing day may signal the end of an ag­greg­a­tion of in­com­ing price quotes. Using a fixed timer for such an event re­duces flex­ib­il­ity be­cause it does not offer much vari­ab­il­ity. Also, a des­ig­nated busi­ness event in the form of an Event Mes­sage allows for cent­ral con­trol of the system. The Ag­greg­ator can listen for the Event Mes­sage on a spe­cial con­trol chan­nel or re­ceive a spe­cially format­ted mes­sage that in­dic­ates the end of the ag­greg­a­tion.

                                                                                                                                                              与完整性条件的选择密切相关的是聚合算法的选择。以下策略是将多条消息压缩为一条消息的常见策略:

                                                                                                                                                              Closely tied to the se­lec­tion of a com­plete­ness con­di­tion is the se­lec­tion of the ag­greg­a­tion al­gorithm. The fol­low­ing strategies are common to con­dense mul­tiple mes­sages into a single mes­sage:

                                                                                                                                                              1. 选择“最佳”答案: 此方法假设存在一个最佳答案,例如同一商品的最低出价。这使得聚合器可以做出决定并仅传递“最佳”消息。然而,在现实生活中,选择标准很少如此简单。例如,物品的最佳出价可能取决于交货时间、可用物品的数量、供应商是否在首选供应商列表上等等。

                                                                                                                                                              2. Select the "best" answer: This ap­proach as­sumes that there is a single best answer, such as the lowest bid for an identical item. This makes it pos­sible for the Ag­greg­ator to make the de­cision and only pass the "best" mes­sage on. How­ever, in real life, se­lec­tion cri­teria are rarely this simple. For ex­ample, the best bid for an item may depend on time of de­liv­ery, the number of avail­able items, whether the vendor is on the pre­ferred vendor list, and so on.

                                                                                                                                                              3. 压缩数据: 聚合器可用于减少来自高流量源的消息流量。在这些情况下,计算单个消息的平均值或将每个消息中的数字字段添加到单个消息中可能是有意义的。如果每条消息都代表一个数值(例如收到的订单数),则此方法效果最佳。

                                                                                                                                                              4. Con­dense data: An Ag­greg­ator can be used to reduce mes­sage traffic from a high-traffic source. In these cases it may make sense to com­pute an av­er­age of in­di­vidual mes­sages or add nu­meric fields from each mes­sage into a single mes­sage. This works best if each mes­sage rep­res­ents a nu­meric value, for ex­ample, the number of orders re­ceived.

                                                                                                                                                              5. 收集数据以供以后评估:聚合器 并不总是能够决定如何选择最佳答案。在这些情况下,使用聚合器来收集各个消息并将它们组合成单个消息仍然是有意义的。该消息可以简单地是各个消息的数据的汇编。聚合决策可以稍后由单独的组件或人员做出。

                                                                                                                                                              6. Col­lect data for later eval­u­ation: It is not always pos­sible for an Ag­greg­ator to make the de­cision of how to select the best answer. In those cases it still makes sense to use an Ag­greg­ator to col­lect the in­di­vidual mes­sages and com­bine them into a single mes­sage. This mes­sage may simply be a com­pil­a­tion of the in­di­vidual mes­sages' data. The ag­greg­a­tion de­cision may be made later by a sep­ar­ate com­pon­ent or a human being.

                                                                                                                                                              在许多情况下,聚合策略是由参数驱动的。例如,等待指定时间的策略可以配置最大等待时间。同样,如果策略是等到报价超过特定阈值,我们很可能会提前让聚合器知道所需的阈值是多少。如果这些参数可在运行时配置,则聚合器可能具有可以接收控制消息(例如这些参数设置)的附加输入。控制消息还可能包含诸如期望的相关消息数量之类的信息,这可以帮助聚合器实施更有效的竣工条件。在这种情况下,聚合器不会在第一条消息到达时简单地启动新聚合,而是接收与预期消息系列相关的预先信息。该信息可以是原始请求消息(例如,分散-聚集消息)的副本,并通过任何必要的参数信息进行扩充。然后,聚合器分配一个新的聚合,并将参数信息与该聚合一起存储(见图)。当各个消息进入时,它们与相应的聚合相关联。我们将此变体称为初始化聚合器,而不是自启动聚合器。显然,只有当我们能够访问原始消息时,这种配置才是可能的,但情况可能并非总是如此。

                                                                                                                                                              In many in­stances, the ag­greg­a­tion strategy is driven by para­met­ers. For ex­ample, a strategy that waits for a spe­cified amount of time can be con­figured with the max­imum wait time. Like­wise, if the strategy is to wait until an offer ex­ceeds a spe­cific threshold, we will most likely let the Ag­greg­ator know in ad­vance what the de­sired threshold is. If these para­met­ers are con­fig­ur­able at runtime, an Ag­greg­ator may fea­ture an ad­di­tional input that can re­ceive con­trol mes­sages, such as these para­meter set­tings. The con­trol mes­sages may also con­tain in­form­a­tion such as the number of cor­rel­ated mes­sages to expect, which can help the Ag­greg­ator im­ple­ment more ef­fect­ive com­ple­tion con­di­tions. In such a scen­ario, the Ag­greg­ator does not simply start a new ag­greg­ate when the first mes­sage ar­rives, but rather re­ceives up-front in­form­a­tion re­lated to an ex­pec­ted series of mes­sages. This in­form­a­tion can be a copy of the ori­ginal re­quest mes­sage (e.g., a Scat­ter-Gather mes­sage) aug­men­ted by any ne­ces­sary para­meter in­form­a­tion. The Ag­greg­ator then al­loc­ates a new ag­greg­ate and stores the para­meter in­form­a­tion with the ag­greg­ate (see figure). When the in­di­vidual mes­sages come in, they are as­so­ci­ated with the cor­res­pond­ing ag­greg­ate. We call this vari­ation an ini­tial­ized Ag­greg­ator as op­posed to the self-start­ing Ag­greg­ator. This con­fig­ur­a­tion, ob­vi­ously, is pos­sible only if we have access to the ori­gin­at­ing mes­sage, which may not always be the case.

                                                                                                                                                              图形/07inf17.gif

                                                                                                                                                              聚合器在许多应用中都很有用。聚合通常与分离器或接收者列表结合以形成复合模式。有关这些复合模式的更详细描述,请参阅组合消息处理器和分散-聚集。

                                                                                                                                                              Ag­greg­ators are useful in many ap­plic­a­tions. The Ag­greg­ator is often coupled with a Split­ter or a Re­cip­i­ent List to form a com­pos­ite pat­tern. See Com­posed Mes­sage Pro­cessor and Scat­ter-Gather for a more de­tailed de­scrip­tion of these com­pos­ite pat­terns.

                                                                                                                                                              示例: 贷款经纪人

                                                                                                                                                              Ex­ample: Loan Broker

                                                                                                                                                              第 9 章“插曲:组合消息”中的组合消息传递示例使用聚合器从银行返回的贷款报价消息中选择最佳贷款报价。贷款经纪人示例使用初始化的聚合器通知接收者聚合器预期的报价消息数量。插曲展示了聚合器​​在Java 、C# 和 TIBCO 中的实现。

                                                                                                                                                              The com­posed mes­saging ex­ample in Chapter 9, "In­ter­lude: Com­posed Mes­saging," uses an Ag­greg­ator to select the best loan quote from the loan quote mes­sages re­turned by the banks. The loan broker ex­ample uses an ini­tial­ized Ag­greg­atorthe Re­cip­i­ent List in­forms the Ag­greg­ator of the number of quote mes­sages to expect. The in­ter­lude shows im­ple­ment­a­tions of the Ag­greg­ator in Java, C# and TIBCO.



                                                                                                                                                              示例: 聚合器作为丢失消息检测器

                                                                                                                                                              Ex­ample: Ag­greg­ator as Miss­ing Mes­sage De­tector

                                                                                                                                                              Joe Walnes 向我们展示了聚合器​​的创造性使用。 他的系统通过一系列组件发送消息,不幸的是,这些组件非常不可靠。即使使用保证传递也无法解决此问题,因为系统本身通常会在使用消息后发生故障。由于应用程序不是事务性客户端,因此正在进行的消息会丢失。为了帮助解决这种情况,Joe 通过两条并行路径路由传入消息:一次通过所需但不可靠的组件,一次使用保证交付绕过组件。聚合重新组合来自两个路径的消息(见图)。

                                                                                                                                                              Joe Walnes showed us a cre­at­ive use of an Ag­greg­ator. His system sends a mes­sage through a se­quence of com­pon­ents, which are un­for­tu­nately quite un­re­li­able. Even using Guar­an­teed De­liv­ery will not cor­rect this prob­lem be­cause the sys­tems them­selves typ­ic­ally fail after con­sum­ing a mes­sage. Be­cause the ap­plic­a­tions are not Trans­ac­tional Cli­ents, the mes­sage-in-pro­gress is lost. To help remedy this situ­ation, Joe routes an in­com­ing mes­sage through two par­al­lel paths: once through the re­quired but un­re­li­able com­pon­ents and once around the com­pon­ents using Guar­an­teed De­liv­ery. An Ag­greg­ator re­com­bines the mes­sages from the two paths (see figure).

                                                                                                                                                              具有超时功能的聚合器检测丢失的消息

                                                                                                                                                              An Ag­greg­ator with Timeout De­tects Miss­ing Mes­sages

                                                                                                                                                              图形/07inf18.gif

                                                                                                                                                              聚合器使用“超时并覆盖”完成条件,这意味着如果达到超时或已接收到两个关联消息,则聚合器完成。聚合算法取决于首先满足哪个条件。如果收到两条消息,则处理后的消息将不加修改地传递。如果发生超时事件,我们就知道其中一个组件发生故障并“吃掉”了消息。因此,我们指示聚合器发布一条错误消息,提醒操作员其中一个组件发生故障。不幸的是,必须手动重新启动组件,但更复杂的配置可能会重新启动组件并重新发送任何丢失的消息。

                                                                                                                                                              The Ag­greg­ator uses a "Timeout with Over­ride" com­plete­ness con­di­tion, which means that the Ag­greg­ator com­pletes if either the timeout is reached or the two as­so­ci­ated mes­sages have been re­ceived. The ag­greg­a­tion al­gorithm de­pends on which con­di­tion is ful­filled first. If two mes­sages are re­ceived, the pro­cessed mes­sage is passed on without modi­fic­a­tion. If the timeout event occurs, we know that one of the com­pon­ents failed and "ate" the mes­sage. As a result, we in­struct the Ag­greg­ator to pub­lish an error mes­sage that alerts the op­er­at­ors that one of the com­pon­ents has failed. Un­for­tu­nately, the com­pon­ents have to be re­star­ted manu­ally, but a more soph­ist­ic­ated con­fig­ur­a­tion could likely re­start the com­pon­ent and resend any lost mes­sages.



                                                                                                                                                              示例: JMS 中的聚合器

                                                                                                                                                              Ex­ample: Ag­greg­ator in JMS

                                                                                                                                                              此示例显示使用Java 消息服务 (JMS) API 的聚合器的实现。聚合接收一个通道上的出价消息,聚合所有相关的出价,并将出价最低的消息发布到另一个通道。出价通过拍卖 ID 属性进行关联,该属性充当消息的关联标识符。聚合策略是接收至少三个投标。聚合器是自启动的,不需要外部初始化。

                                                                                                                                                              This ex­ample shows the im­ple­ment­a­tion of an Ag­greg­ator using the Java Mes­saging Ser­vice (JMS) API. The Ag­greg­ator re­ceives bid mes­sages on one chan­nel, ag­greg­ates all re­lated bids, and pub­lishes a mes­sage with the lowest bid to an­other chan­nel. Bids are cor­rel­ated through an Auc­tion ID prop­erty that acts as a Cor­rel­a­tion Iden­ti­fier for the mes­sages. The ag­greg­a­tion strategy is to re­ceive a min­imum of three bids. The Ag­greg­ator is self-start­ing and does not re­quire ex­ternal ini­tial­iz­a­tion.

                                                                                                                                                              聚合器示例选择最低出价

                                                                                                                                                              The Ag­greg­ator Ex­ample Se­lects the Lowest Bid

                                                                                                                                                              图形/07inf19.gif

                                                                                                                                                              该解决方案由以下主要类组成(见下图):

                                                                                                                                                              The solu­tion con­sists of the fol­low­ing main classes (see the fol­low­ing figure):

                                                                                                                                                              1. 聚合器包含接收消息、聚合消息和发送结果消息的逻辑。通过Aggregate接口与聚合进行交互。

                                                                                                                                                              2. Ag­greg­ator con­tains logic to re­ceive mes­sages, ag­greg­ate them, and send result mes­sages. In­ter­faces with ag­greg­ates via the Ag­greg­ate in­ter­face.

                                                                                                                                                              3. AuctionAggregate实现Aggregate 接口。该类充当Aggregate接口和Auction类之间的适配器 [ GoF ]。此设置允许Auction类不引用 JMS API。

                                                                                                                                                              4. Auc­tion­Ag­greg­ate im­ple­ments the Ag­greg­ate in­ter­face. This class acts as an Ad­apter [GoF] between the Ag­greg­ate in­ter­face and the Auc­tion class. This setup allows the Auc­tion class to be free of ref­er­ences to the JMS API.

                                                                                                                                                              5. 拍卖是已收到的相关投标的集合。Auction类实现聚合策略,例如,查找最低出价并确定聚合何时完成。

                                                                                                                                                              6. Auc­tion is a col­lec­tion of re­lated bids that have been re­ceived. The Auc­tion class im­ple­ments the ag­greg­a­tion strategy, for ex­ample, find­ing the lowest bid and de­term­in­ing when the ag­greg­ate is com­plete.

                                                                                                                                                              7. Bid是一个便利类,保存与出价相关的数据项。我们将传入的消息数据转换为Bid对象,以便我们可以通过强类型接口访问出价数据,从而使拍卖逻辑完全独立于 JMS API。

                                                                                                                                                              8. Bid is a con­veni­ence class that holds the data items as­so­ci­ated with a bid. We con­vert in­com­ing mes­sage data into a Bid object so that we can access the bid data through a strongly typed in­ter­face, making the Auc­tion logic com­pletely in­de­pend­ent from the JMS API.

                                                                                                                                                              拍卖聚合器类图

                                                                                                                                                              Auc­tion Ag­greg­ator Class Dia­gram

                                                                                                                                                              图形/07inf20.gif

                                                                                                                                                              该解决方案的核心是Aggregator类。该类需要两个 JMS Destination:一个用于输入,另一个用于输出。目标是队列点对点通道)或主题(发布-订阅通道)的 JMS 抽象。这种抽象允许我们编写独立于通道类型的 JMS 代码。此功能对于测试和调试非常有用。例如,在测试过程中,我们可以使用发布-订阅主题,以便我们可以轻松地“监听”消息流量。当我们投入生产时,我们可能想切换到队列。

                                                                                                                                                              The core of the solu­tion is the Ag­greg­ator class. This class re­quires two JMS Des­tin­a­tions: one for input and an­other for output. Des­tin­a­tion is the JMS ab­strac­tion for a Queue (Point-to-Point Chan­nel ) or a Topic (Pub­lish-Sub­scribe Chan­nel ). The ab­strac­tion allows us to write JMS code in­de­pend­ent from the type of chan­nel. This fea­ture can be very useful for test­ing and de­bug­ging. For ex­ample, during test­ing we may use pub­lish-sub­scribe topics so that we can easily "listen in" on the mes­sage traffic. When we go to pro­duc­tion, we may want to switch to queues.

                                                                                                                                                              
                                                                                                                                                              公共类聚合器实现 MessageListener
                                                                                                                                                              {
                                                                                                                                                                  静态最终字符串 PROP_CORRID = "拍卖ID";
                                                                                                                                                              
                                                                                                                                                                  映射 activeAggregates = new HashMap();
                                                                                                                                                              
                                                                                                                                                                  目标输入Dest = null;
                                                                                                                                                                  目标输出Dest = null;
                                                                                                                                                                  会话会话=空;
                                                                                                                                                              
                                                                                                                                                                  消息消费者= null;
                                                                                                                                                                  MessageProducer 输出 = null;
                                                                                                                                                              
                                                                                                                                                                  公共聚合器(目的地输入目的地,目的地
                                                                                                                                                              图形/ccc.gif输出目标,会话会话)
                                                                                                                                                                  {
                                                                                                                                                                      this.inputDest = inputDest;
                                                                                                                                                                      this.outputDest = 输出目标;
                                                                                                                                                                      this.session = 会话;
                                                                                                                                                                  }
                                                                                                                                                              
                                                                                                                                                                  公共无效运行()
                                                                                                                                                                  {
                                                                                                                                                                      尝试 {
                                                                                                                                                                          in = session.createConsumer(inputDest);
                                                                                                                                                                          输出 = session.createProducer(outputDest);
                                                                                                                                                                          in.setMessageListener(this);
                                                                                                                                                                      } catch (异常 e) {
                                                                                                                                                                          System.out.println("发生异常:" + e
                                                                                                                                                              图形/ccc.gif.toString());
                                                                                                                                                                      }
                                                                                                                                                                  }
                                                                                                                                                              
                                                                                                                                                                  公共无效onMessage(消息msg)
                                                                                                                                                                  {
                                                                                                                                                                      尝试 {
                                                                                                                                                                          字符串相关 ID = msg.getStringProperty
                                                                                                                                                              图形/ccc.gif(PROP_CORRID);
                                                                                                                                                                          聚合聚合 = (Aggregate)activeAggregates.get
                                                                                                                                                              图形/ccc.gif(相关ID);
                                                                                                                                                                          如果(聚合==空){
                                                                                                                                                                              聚合 = 新 AuctionAggregate(会话);
                                                                                                                                                                              activeAggregates.put(correlationID, 聚合);
                                                                                                                                                                          }
                                                                                                                                                                          //--- 如果聚合已经关闭则忽略消息
                                                                                                                                                                          if (!aggregate.isComplete()) {
                                                                                                                                                                              聚合.addMessage(msg);
                                                                                                                                                                              if (aggregate.isComplete()) {
                                                                                                                                                                                  MapMessage 结果 = (MapMessage)聚合
                                                                                                                                                              图形/ccc.gif.getResultMessage();
                                                                                                                                                                                  发送(结果);
                                                                                                                                                                              }
                                                                                                                                                                          }
                                                                                                                                                                      } catch (JMSException e) {
                                                                                                                                                                          System.out.println("发生异常:" + e
                                                                                                                                                              图形/ccc.gif.toString());
                                                                                                                                                                      }
                                                                                                                                                                  }
                                                                                                                                                              }
                                                                                                                                                              
                                                                                                                                                              
                                                                                                                                                              public class Ag­greg­ator im­ple­ments Mes­sageL­istener
                                                                                                                                                              {
                                                                                                                                                                  static final String PROP_­COR­RID = "Auc­tionID";
                                                                                                                                                              
                                                                                                                                                                  Map act­iveAg­greg­ates = new HashMap();
                                                                                                                                                              
                                                                                                                                                                  Des­tin­a­tion in­put­Dest = null;
                                                                                                                                                                  Des­tin­a­tion out­put­Dest = null;
                                                                                                                                                                  Ses­sion ses­sion = null;
                                                                                                                                                              
                                                                                                                                                                  Mes­sage­Con­sumer in = null;
                                                                                                                                                                  Mes­sage­Pro­du­cer out = null;
                                                                                                                                                              
                                                                                                                                                                  public Ag­greg­ator (Des­tin­a­tion in­put­Dest, Des­tin­a­tion
                                                                                                                                                               out­put­Dest, Ses­sion ses­sion)
                                                                                                                                                                  {
                                                                                                                                                                      this.in­put­Dest = in­put­Dest;
                                                                                                                                                                      this.out­put­Dest = out­put­Dest;
                                                                                                                                                                      this.ses­sion = ses­sion;
                                                                                                                                                                  }
                                                                                                                                                              
                                                                                                                                                                  public void run()
                                                                                                                                                                  {
                                                                                                                                                                      try {
                                                                                                                                                                          in = ses­sion.cre­ate­Con­sumer(in­put­Dest);
                                                                                                                                                                          out = ses­sion.cre­ate­Pro­du­cer(out­put­Dest);
                                                                                                                                                                          in.set­Mes­sageL­istener(this);
                                                                                                                                                                      } catch (Ex­cep­tion e) {
                                                                                                                                                                          System.out.println("Ex­cep­tion oc­curred: " + e
                                                                                                                                                              .to­String());
                                                                                                                                                                      }
                                                                                                                                                                  }
                                                                                                                                                              
                                                                                                                                                                  public void on­Mes­sage(Mes­sage msg)
                                                                                                                                                                  {
                                                                                                                                                                      try {
                                                                                                                                                                          String cor­rel­a­tionID = msg.get­String­Prop­erty
                                                                                                                                                              (PROP_­COR­RID);
                                                                                                                                                                          Ag­greg­ate ag­greg­ate = (Ag­greg­ate)act­iveAg­greg­ates.get
                                                                                                                                                              (cor­rel­a­tionID);
                                                                                                                                                                          if (ag­greg­ate == null) {
                                                                                                                                                                              ag­greg­ate = new Auc­tion­Ag­greg­ate(ses­sion);
                                                                                                                                                                              act­iveAg­greg­ates.put(cor­rel­a­tionID, ag­greg­ate);
                                                                                                                                                                          }
                                                                                                                                                                          //--- ignore mes­sage if ag­greg­ate is already closed
                                                                                                                                                                          if (!ag­greg­ate.isCom­plete()) {
                                                                                                                                                                              ag­greg­ate.addMes­sage(msg);
                                                                                                                                                                              if (ag­greg­ate.isCom­plete()) {
                                                                                                                                                                                  MapMes­sage result = (MapMes­sage)ag­greg­ate
                                                                                                                                                              .getRes­ult­Mes­sage();
                                                                                                                                                                                  out.send(result);
                                                                                                                                                                              }
                                                                                                                                                                          }
                                                                                                                                                                      } catch (JM­SEx­cep­tion e) {
                                                                                                                                                                          System.out.println("Ex­cep­tion oc­curred: " + e
                                                                                                                                                              .to­String());
                                                                                                                                                                      }
                                                                                                                                                                  }
                                                                                                                                                              }
                                                                                                                                                              

                                                                                                                                                              Aggregator一个事件驱动的 Consumer,实现了MessageListener 接口,这要求它实现 onMessage方法。因为AggregatorMessageConsumer 的消息侦听器,所以每次新消息到达使用者的目的地时,JMS 都会调用 onMesssage 方法。 对于每条传入消息,聚合器都会提取相关 ID(存储为消息属性)并检查该相关 ID 是否存在活动聚合。如果没有找到聚合,聚合器会实例化一个新的聚合AuctionAggregate 实例然后聚合器检查聚合是否仍处于活动状态(即,不完整)。如果聚合不再处于活动状态,它将丢弃传入的消息。如果聚合处于活动状态,它将消息添加到聚合并测试是否已满足终止条件。如果是,它将获得最佳出价条目并发布。

                                                                                                                                                              The Ag­greg­ator is an Event-Driven Con­sumer and im­ple­ments the Mes­sageL­istener in­ter­face, which re­quires it to im­ple­ment the on­Mes­sage method. Be­cause the Ag­greg­ator is the mes­sage listener for the Mes­sage­Con­sumer, every time a new mes­sage ar­rives on the con­sumer's des­tin­a­tion, JMS in­vokes the method on­Mess­sage. For each in­com­ing mes­sage, the Ag­greg­ator ex­tracts the cor­rel­a­tion ID (stored as a mes­sage prop­erty) and checks whether an active ag­greg­ate exists for this cor­rel­a­tion ID. If no ag­greg­ate is found, the Ag­greg­ator in­stan­ti­ates a new Auc­tion­Ag­greg­ate in­stance. The Ag­greg­ator then checks whether the ag­greg­ate is still active (i.e., not com­plete). If the ag­greg­ate is no longer active, it dis­cards the in­com­ing mes­sage. If the ag­greg­ate is active, it adds the mes­sage to the ag­greg­ate and tests whether the ter­min­a­tion con­di­tion has been ful­filled. If so, it gets the best bid entry and pub­lishes it.

                                                                                                                                                              聚合器代码非常通用,仅用两行代码依赖于这个特定的示例应用程序。首先,它假设相关 ID 存储在消息属性AuctionID 中。其次,它创建 AuctionAggregate 类的实例。 如果我们使用返回 Aggregate 类型的对象在内部创建AuctionAggregate工厂,则可以避免此引用。由于这是一本关于企业集成而不是面向对象设计的书,因此我们保持简单并忽略了这种依赖性。

                                                                                                                                                              The Ag­greg­ator code is very gen­eric and de­pends on this spe­cific ex­ample ap­plic­a­tion only in two lines of code. First, it as­sumes that the cor­rel­a­tion ID is stored in the mes­sage prop­erty Auc­tionID. Second, it cre­ates an in­stance of the class Auc­tion­Ag­greg­ate. We could avoid this ref­er­ence if we used a fact­ory that re­turns an object of type Ag­greg­ate and in­tern­ally cre­ates an in­stance of type Auc­tion­Ag­greg­ate. Since this is a book on en­ter­prise in­teg­ra­tion and not on object-ori­ented design, we kept things simple and let this de­pend­ency pass.

                                                                                                                                                              AuctionAggregate提供Aggregate 接口的实现。该接口相当简单,仅指定三个方法:一种用于添加新消息 ( addMessage ) ,一种用于确定聚合是否完成 ( isComplete ),一种用于获取最佳结果 ( getBestMessage ) 。

                                                                                                                                                              The Auc­tion­Ag­greg­ate class provides the im­ple­ment­a­tion for the Ag­greg­ate in­ter­face. The in­ter­face is rather simple, spe­cify­ing only three meth­ods: one to add a new mes­sage (addMes­sage), one to de­term­ine whether the ag­greg­ate is com­plete (isCom­plete), and one to get the best result (get­Be­st­Mes­sage).

                                                                                                                                                              公共接口聚合{
                                                                                                                                                                  公共无效addMessage(消息消息);
                                                                                                                                                                  公共布尔 isComplete();
                                                                                                                                                                  公共消息 getResultMessage();
                                                                                                                                                              }
                                                                                                                                                              
                                                                                                                                                              public in­ter­face Ag­greg­ate {
                                                                                                                                                                  public void addMes­sage(Mes­sage mes­sage);
                                                                                                                                                                  public boolean isCom­plete();
                                                                                                                                                                  public Mes­sage getRes­ult­Mes­sage();
                                                                                                                                                              }
                                                                                                                                                              

                                                                                                                                                              我们决定创建一个单独的 Auction 类来实现聚合策略,但不依赖于 JMS API,而不是在 AuctionAggregate 类中实现聚合策略:

                                                                                                                                                              In­stead of im­ple­ment­ing the ag­greg­a­tion strategy inside the Auc­tion­Ag­greg­ate class, we de­cided to create a sep­ar­ate class Auc­tion that im­ple­ments the ag­greg­a­tion strategy but is not de­pend­ent on the JMS API:

                                                                                                                                                              公开课拍卖
                                                                                                                                                              {
                                                                                                                                                                  ArrayList 出价 = new ArrayList();
                                                                                                                                                              
                                                                                                                                                                  公共无效addBid(出价出价)
                                                                                                                                                                  {
                                                                                                                                                                      出价。添加(出价);
                                                                                                                                                                      System.out.println(bids.size() + "拍卖中的出价。");
                                                                                                                                                                  }
                                                                                                                                                              
                                                                                                                                                                  公共布尔值 isComplete()
                                                                                                                                                                  {
                                                                                                                                                                      返回 (出价.size() >= 3);
                                                                                                                                                                  }
                                                                                                                                                              
                                                                                                                                                                  公开出价 getBestBid()
                                                                                                                                                                  {
                                                                                                                                                                      出价 bestBid = null;
                                                                                                                                                              
                                                                                                                                                                      迭代器 iter = bids.iterator();
                                                                                                                                                                      if (iter.hasNext())
                                                                                                                                                                          bestBid = (出价) iter.next();
                                                                                                                                                              
                                                                                                                                                                      while (iter.hasNext()) {
                                                                                                                                                                          出价 b = (出价) iter.next();
                                                                                                                                                                          if (b.getPrice() < bestBid.getPrice()) {
                                                                                                                                                                              最佳出价=b;
                                                                                                                                                                          }
                                                                                                                                                                      }
                                                                                                                                                                      返回最佳出价;
                                                                                                                                                                  }
                                                                                                                                                              }
                                                                                                                                                              
                                                                                                                                                              public class Auc­tion
                                                                                                                                                              {
                                                                                                                                                                  Ar­rayL­ist bids = new Ar­rayL­ist();
                                                                                                                                                              
                                                                                                                                                                  public void addBid(Bid bid)
                                                                                                                                                                  {
                                                                                                                                                                      bids.add(bid);
                                                                                                                                                                      System.out.println(bids.size() + " Bids in auc­tion.");
                                                                                                                                                                  }
                                                                                                                                                              
                                                                                                                                                                  public boolean isCom­plete()
                                                                                                                                                                  {
                                                                                                                                                                      return (bids.size() >= 3);
                                                                                                                                                                  }
                                                                                                                                                              
                                                                                                                                                                  public Bid get­Be­st­Bid()
                                                                                                                                                                  {
                                                                                                                                                                      Bid be­st­Bid = null;
                                                                                                                                                              
                                                                                                                                                                      Iter­ator iter = bids.iter­ator();
                                                                                                                                                                      if (iter.has­Next())
                                                                                                                                                                          be­st­Bid = (Bid) iter.next();
                                                                                                                                                              
                                                                                                                                                                      while (iter.has­Next()) {
                                                                                                                                                                          Bid b = (Bid) iter.next();
                                                                                                                                                                          if (b.get­Price() < be­st­Bid.get­Price()) {
                                                                                                                                                                              be­st­Bid = b;
                                                                                                                                                                          }
                                                                                                                                                                      }
                                                                                                                                                                      return be­st­Bid;
                                                                                                                                                                  }
                                                                                                                                                              }
                                                                                                                                                              

                                                                                                                                                              Auction类实际上非常简单。它提供了三个与Aggregate接口类似的方法,但方法签名的不同之处在于它们使用强类型 Bid而不是 JMS 特定的Message类。对于此示例,资格条件非常简单,只需等待收到三个投标即可。然而,将聚合策略与Auction类和 JMS API 分开,可以轻松增强Auction类以合并更复杂的逻辑。

                                                                                                                                                              The Auc­tion class is ac­tu­ally quite simple. It provides three meth­ods sim­ilar to the Ag­greg­ate in­ter­face, but the method sig­na­tures differ in that they use the strongly typed Bid class in­stead of the JMS-spe­cific Mes­sage class. For this ex­ample, the com­pete­ness con­di­tion is very simple, simply wait­ing until three bids have been re­ceived. How­ever, sep­ar­at­ing the ag­greg­a­tion strategy from the Auc­tion class and the JMS API makes it easy to en­hance the Auc­tion class to in­cor­por­ate more soph­ist­ic­ated logic.

                                                                                                                                                              AuctionAggregate类充当Aggregate接口和Auction 类之间的适配器 [GoF] 适配器是将一个类的接口转换为另一个接口的类。

                                                                                                                                                              The Auc­tion­Ag­greg­ate class acts as an Ad­apter [GoF] between the Ag­greg­ate in­ter­face and the Auc­tion class. An ad­apter is a class that con­verts the in­ter­face of a class into an­other in­ter­face.

                                                                                                                                                              
                                                                                                                                                              公共类 AuctionAggregate 实现聚合 {
                                                                                                                                                                  static String PROP_AUCTIONID = "拍卖ID";
                                                                                                                                                                  静态字符串 ITEMID = "ItemID";
                                                                                                                                                                  静态字符串 VENDOR = "供应商";
                                                                                                                                                                  静态字符串 PRICE = "价格";
                                                                                                                                                              
                                                                                                                                                                  私人会议;
                                                                                                                                                                  私下拍卖;
                                                                                                                                                              
                                                                                                                                                                  公开拍卖聚合(会话会话)
                                                                                                                                                                  {
                                                                                                                                                                      this.session = 会话;
                                                                                                                                                                      拍卖=新拍卖();
                                                                                                                                                                  }
                                                                                                                                                              
                                                                                                                                                                  公共无效addMessage(消息消息){
                                                                                                                                                                      出价 出价=空;
                                                                                                                                                                      if (MapMessage 的消息实例) {
                                                                                                                                                                          尝试 {
                                                                                                                                                                              MapMessage mapmsg = (MapMessage)消息;
                                                                                                                                                                              字符串拍卖ID = mapmsg.getStringProperty
                                                                                                                                                              图形/ccc.gif(PROP_拍卖ID);
                                                                                                                                                                              字符串 itemID = mapmsg.getString(ITEMID);
                                                                                                                                                                              字符串供应商 = mapmsg.getString(VENDOR);
                                                                                                                                                                              双倍价格 = mapmsg.getDouble(PRICE);
                                                                                                                                                                              出价 = 新出价(auctionID, itemID, 供应商, 价格);
                                                                                                                                                                              拍卖.addBid(出价);
                                                                                                                                                                          } catch (JMSException e) {
                                                                                                                                                                              System.out.println(e.getMessage());
                                                                                                                                                              
                                                                                                                                                                          }
                                                                                                                                                                      }
                                                                                                                                                                  }
                                                                                                                                                              
                                                                                                                                                                  公共布尔值 isComplete()
                                                                                                                                                                  {
                                                                                                                                                                      返回拍卖.isComplete();
                                                                                                                                                                  }
                                                                                                                                                              
                                                                                                                                                                  公共消息 getResultMessage() {
                                                                                                                                                                      出价 出价 = auction.getBestBid();
                                                                                                                                                                      尝试 {
                                                                                                                                                                          MapMessage msg = session.createMapMessage();
                                                                                                                                                                          msg.setStringProperty(PROP_AUCTIONID, 出价
                                                                                                                                                              图形/ccc.gif.getCorrelationID());
                                                                                                                                                                          msg.setString(ITEMID, bid.getItemID());
                                                                                                                                                                          msg.setString(VENDOR, bid.getVendorName());
                                                                                                                                                                          msg.setDouble(PRICE, bid.getPrice());
                                                                                                                                                                          返回消息;
                                                                                                                                                                      } catch (JMSException e) {
                                                                                                                                                                          System.out.println("无法创建消息:" + e
                                                                                                                                                              图形/ccc.gif.getMessage());
                                                                                                                                                                          返回空值;
                                                                                                                                                                      }
                                                                                                                                                                   }
                                                                                                                                                              }
                                                                                                                                                              
                                                                                                                                                              
                                                                                                                                                              public class Auc­tion­Ag­greg­ate im­ple­ments Ag­greg­ate {
                                                                                                                                                                  static String PROP_AUC­TIONID = "Auc­tionID";
                                                                                                                                                                  static String ITEMID = "ItemID";
                                                                                                                                                                  static String VENDOR = "Vendor";
                                                                                                                                                                  static String PRICE = "Price";
                                                                                                                                                              
                                                                                                                                                                  private Ses­sion ses­sion;
                                                                                                                                                                  private Auc­tion auc­tion;
                                                                                                                                                              
                                                                                                                                                                  public Auc­tion­Ag­greg­ate(Ses­sion ses­sion)
                                                                                                                                                                  {
                                                                                                                                                                      this.ses­sion = ses­sion;
                                                                                                                                                                      auc­tion = new Auc­tion();
                                                                                                                                                                  }
                                                                                                                                                              
                                                                                                                                                                  public void addMes­sage(Mes­sage mes­sage) {
                                                                                                                                                                      Bid bid = null;
                                                                                                                                                                      if (mes­sage in­stanceof MapMes­sage) {
                                                                                                                                                                          try {
                                                                                                                                                                              MapMes­sage mapmsg = (MapMes­sage)mes­sage;
                                                                                                                                                                              String auc­tionID = mapmsg.get­String­Prop­erty
                                                                                                                                                              (PROP_AUC­TIONID);
                                                                                                                                                                              String itemID = mapmsg.get­String(ITEMID);
                                                                                                                                                                              String vendor = mapmsg.get­String(VENDOR);
                                                                                                                                                                              double price = mapmsg.get­Double(PRICE);
                                                                                                                                                                              bid = new Bid(auc­tionID, itemID, vendor, price);
                                                                                                                                                                              auc­tion.addBid(bid);
                                                                                                                                                                          } catch (JM­SEx­cep­tion e) {
                                                                                                                                                                              System.out.println(e.get­Mes­sage());
                                                                                                                                                              
                                                                                                                                                                          }
                                                                                                                                                                      }
                                                                                                                                                                  }
                                                                                                                                                              
                                                                                                                                                                  public boolean isCom­plete()
                                                                                                                                                                  {
                                                                                                                                                                      return auc­tion.isCom­plete();
                                                                                                                                                                  }
                                                                                                                                                              
                                                                                                                                                                  public Mes­sage getRes­ult­Mes­sage() {
                                                                                                                                                                      Bid bid = auc­tion.get­Be­st­Bid();
                                                                                                                                                                      try {
                                                                                                                                                                          MapMes­sage msg = ses­sion.cre­ateM­apMes­sage();
                                                                                                                                                                          msg.set­String­Prop­erty(PROP_AUC­TIONID, bid
                                                                                                                                                              .get­Cor­rel­a­tionID());
                                                                                                                                                                          msg.set­String(ITEMID, bid.getItemID());
                                                                                                                                                                          msg.set­String(VENDOR, bid.getVendorName());
                                                                                                                                                                          msg.set­Double(PRICE, bid.get­Price());
                                                                                                                                                                          return msg;
                                                                                                                                                                      } catch (JM­SEx­cep­tion e) {
                                                                                                                                                                          System.out.println("Could not create mes­sage: " + e
                                                                                                                                                              .get­Mes­sage());
                                                                                                                                                                          return null;
                                                                                                                                                                      }
                                                                                                                                                                   }
                                                                                                                                                              }
                                                                                                                                                              

                                                                                                                                                              下面的序列图总结了类之间的交互:

                                                                                                                                                              The fol­low­ing se­quence dia­gram sum­mar­izes the in­ter­ac­tion between the classes:

                                                                                                                                                              拍卖聚合器序列图

                                                                                                                                                              Auc­tion Ag­greg­ator Se­quence Dia­gram

                                                                                                                                                              图形/07inf21.gif

                                                                                                                                                              这个简单的示例假设拍卖 ID 是普遍唯一的。这使我们不必担心清理公开拍卖列表,我们只是让它增长。在现实应用程序中,我们需要决定何时清除旧的拍卖记录以避免内存泄漏。

                                                                                                                                                              This simple ex­ample as­sumes that Auc­tion IDs are uni­ver­sally unique. This allows us to not worry about clean­ing up the open auc­tion listwe just let it grow. In a real-life ap­plic­a­tion we would need to decide when to purge old auc­tion re­cords to avoid memory leaks.

                                                                                                                                                              因为此代码仅引用 JMS Destinations ,所以我们可以使用 TopicQueue来运行它。 在生产环境中,此应用程序可能更可能采用点对点通道(相当于 JMS队列) ,因为应该只有一个投标接收者,即聚合器如发布-订阅通道中所述、主题可以简化测试和调试。在不影响消息流的情况下向主题添加额外的侦听器非常容易。调试消息传递应用程序时,运行一个单独的侦听器窗口来跟踪任何参与者之间交换的所有消息通常很有用。许多 JMS 实现允许您在主题名称中使用通配符,以便侦听器可以通过指定主题名称*来简单地订阅所有主题。有一个简单的侦听器工具非常方便,它可以显示有关某个主题的所有消息,并将消息记录到文件中以供以后分析。

                                                                                                                                                              Be­cause this code ref­er­ences only JMS Des­tin­a­tions, we can run it with either Topics or Queues. In a pro­duc­tion en­vir­on­ment, this ap­plic­a­tion may be more likely to employ a Point-to-Point Chan­nel (equi­val­ent to a JMS Queue) be­cause there should be only a single re­cip­i­ent for a bid, the Ag­greg­ator. As de­scribed in Pub­lish-Sub­scribe Chan­nel, topics can sim­plify test­ing and de­bug­ging. It is very easy to add an ad­di­tional listener to a topic without af­fect­ing the flow of mes­sages. When de­bug­ging a mes­saging ap­plic­a­tion, it is often useful to run a sep­ar­ate listener window that tracks all mes­sages that are ex­changed between any par­ti­cipants. Many JMS im­ple­ment­a­tions allow you to use wild­cards in topic names so that a listener can simply sub­scribe to all topics by spe­cify­ing a topic name of *. It is very handy to have a simple listener tool that dis­plays all mes­sages trav­el­ing on a topic and also logs the mes­sages into a file for later ana­lysis.



                                                                                                                                                                重排序器

                                                                                                                                                                Resequencer

                                                                                                                                                                图形/resequencer_icon.gif

                                                                                                                                                                消息路由器可以根据消息内容或其他标准将消息从一个通道路由到不同的通道。由于各个消息可能遵循不同的路线,因此某些消息可能会比其他消息更早地通过处理步骤,从而导致消息乱序。然而,某些后续处理步骤确实需要按顺序处理消息,例如,为了维护引用完整性。

                                                                                                                                                                A Mes­sage Router can route mes­sages from one chan­nel to dif­fer­ent chan­nels based on mes­sage con­tent or other cri­teria. Be­cause in­di­vidual mes­sages may follow dif­fer­ent routes, some mes­sages are likely to pass through the pro­cess­ing steps sooner than others, res­ult­ing in the mes­sages get­ting out of order. How­ever, some sub­se­quent pro­cess­ing steps do re­quire in-se­quence pro­cess­ing of mes­sages, for ex­ample, to main­tain ref­er­en­tial in­teg­rity.

                                                                                                                                                                我们如何才能将相关但无序的消息流恢复到正确的顺序?

                                                                                                                                                                How can we get a stream of re­lated but out-of-se­quence mes­sages back into the cor­rect order?



                                                                                                                                                                解决乱序问题的明显方法是首先保持消息的顺序。事实上,让事物保持秩序比让它们恢复秩序更容易。这就是为什么许多大学图书馆喜欢阻止读者将书籍放回(订购的)书架。通过控制插入过程,(几乎)在任何时间点都能保证正确的顺序。但是,在处理异步消息传递解决方案时保持事情按顺序进行可能就像说服青少年让她的房间保持秩序实际上是更有效的方法一样困难。

                                                                                                                                                                The ob­vi­ous solu­tion to the out-of-se­quence prob­lem is to keep mes­sages in se­quence in the first place. Keep­ing things in order is in fact easier than get­ting them back in order. That's why many uni­ver­sity lib­rar­ies like to pre­vent read­ers from put­ting books back into the (ordered) book­shelf. By con­trolling the insert pro­cess, cor­rect order is (almost) guar­an­teed at any point in time. But keep­ing things in se­quence when deal­ing with an asyn­chron­ous mes­saging solu­tion can be about as dif­fi­cult as con­vin­cing a teen­ager that keep­ing her room in order is ac­tu­ally the more ef­fi­cient ap­proach.

                                                                                                                                                                事情发生混乱的一种常见方式是不同的消息采用不同的处理路径。让我们看一个简单的例子。假设我们正在处理一个编号的消息序列。如果所有偶数消息都必须经过特殊转换,而所有奇数消息都可以直接通过,那么奇数消息将立即出现在结果通道上,而偶数消息则在转换处排队。如果转换非常慢,则在单个偶数消息出现之前,所有奇数消息可能会出现在输出通道上,从而使序列完全乱序(请参见下页顶部的图)。

                                                                                                                                                                One common way things get out of se­quence is when dif­fer­ent mes­sages take dif­fer­ent pro­cess­ing paths. Let's look at a simple ex­ample. Let's assume we are deal­ing with a numbered se­quence of mes­sages. If all even-numbered mes­sages have to un­dergo a spe­cial trans­form­a­tion, whereas all odd-numbered mes­sages can be passed right through, then odd-numbered mes­sages will appear on the res­ult­ing chan­nel im­me­di­ately while the even ones queue up at the trans­form­a­tion. If the trans­form­a­tion is quite slow, all odd mes­sages may appear on the output chan­nel before a single even mes­sage makes it, bring­ing the se­quence com­pletely out of order (see figure on the top of the fol­low­ing page).

                                                                                                                                                                消息乱序

                                                                                                                                                                Mes­sages Get­ting Out of Order

                                                                                                                                                                图形/07inf22.gif

                                                                                                                                                                为了避免消息乱序,我们可以引入一种环回(确认)机制,确保一次只有一条消息通过系统,这意味着直到最后一条消息处理完成后才会发送下一条消息。这种保守的方法可以解决这个问题,但有两个明显的缺点。首先,它会显着降低系统速度。如果我们有大量并行处理单元,我们将严重未充分利用处理能力。事实上,在许多情况下,并行处理的原因是我们需要提高性能,因此一次限制一条消息的流量将完全否定解决方案的目的。第二个问题是,这种方法要求我们控制发送到处理单元的消息。然而,我们经常发现自己处于无序消息流的接收端,而无法控制消息源。

                                                                                                                                                                To avoid get­ting the mes­sages out of order, we could in­tro­duce a loop-back (ac­know­ledg­ment) mech­an­ism that makes sure that only one mes­sage at a time passes through the system, mean­ing the next mes­sage will not be sent until the last one is done pro­cess­ing. This con­ser­vat­ive ap­proach will re­solve the issue, but has two sig­ni­fic­ant draw­backs. First, it can slow the system sig­ni­fic­antly. If we have a large number of par­al­lel pro­cess­ing units, we would severely un­der­u­til­ize the pro­cess­ing power. In fact, in many in­stances the reason for par­al­lel pro­cess­ing is that we need to in­crease per­form­ance, so throt­tling traffic to one mes­sage at a time would com­pletely negate the pur­pose of the solu­tion. The second issue is that this ap­proach re­quires us to have con­trol over mes­sages being sent into the pro­cess­ing units. How­ever, we often find ourselves at the re­ceiv­ing end of an out-of-se­quence mes­sage stream without having con­trol over the mes­sage origin.

                                                                                                                                                                聚合器可以接收消息流,识别相关消息,并根据多种策略将它们聚合成单个消息。在此过程中,聚合器还必须处理单个消息可以在任何时间以任何顺序到达的事实。聚合器通过存储消息直到所有相关消息到达后再发布结果消息来解决此问题。

                                                                                                                                                                An Ag­greg­ator can re­ceive a stream of mes­sages, identify re­lated mes­sages, and ag­greg­ate them into a single mes­sage based on a number of strategies. During this pro­cess, the Ag­greg­ator also must deal with the fact that in­di­vidual mes­sages can arrive at any time and in any order. The Ag­greg­ator solves this prob­lem by stor­ing mes­sages until all re­lated mes­sages arrive before it pub­lishes a result mes­sage.

                                                                                                                                                                使用有状态过滤器(Resequencer)来收集消息并重新排序,以便它们可以按指定的顺序发布到输出通道。

                                                                                                                                                                Use a state­ful filter, a Resequen­cer, to col­lect and re­order mes­sages so that they can be pub­lished to the output chan­nel in a spe­cified order.

                                                                                                                                                                图形/07inf23.gif



                                                                                                                                                                重排序器可以接收可能不按顺序到达的消息流。它将失序的消息存储在内部缓冲区中,直到获得完整的序列,然后以正确的顺序将消息发布到输出通道。输出通道保持顺序非常重要,这样可以保证消息按顺序到达下一个组件。与大多数其他路由器一样,重排序器通常不会修改消息内容。

                                                                                                                                                                The Resequen­cer can re­ceive a stream of mes­sages that may not arrive in order. It stores out-of-se­quence mes­sages in an in­ternal buffer until a com­plete se­quence is ob­tained, and then pub­lishes the mes­sages to the output chan­nel in the proper se­quence. It is im­port­ant that the output chan­nel is order-pre­serving so mes­sages are guar­an­teed to arrive in order at the next com­pon­ent. Like most other routers, a Resequen­cer usu­ally does not modify the mes­sage con­tents.

                                                                                                                                                                序列号

                                                                                                                                                                Se­quence Num­bers

                                                                                                                                                                为了使重排序器发挥作用,每条消息都必须有一个唯一的序列号(请参阅消息序列) 。该序列号不同于消息标识符或相关标识符。消息标识符是唯一标识每条消息的特殊属性。然而,在大多数情况下,消息标识符是不具有可比性的;它们基本上是随机值,通常甚至不是数字。即使它们碰巧具有数值,在现有消息标识符元素上重载序列号语义通常也是一个坏主意。相关标识符旨在将传入消息与原始出站请求进行匹配(请参阅请求-答复)。相关标识符的唯一要求唯一性它们不必是数字或按顺序排列。因此,如果我们需要保留一系列消息的顺序,我们应该定义一个单独的字段来跟踪序列中每条消息的位置。通常,该字段可以是消息标头的一部分。

                                                                                                                                                                For the Resequen­cer to func­tion, each mes­sage has to have a unique se­quence number (see Mes­sage Se­quence ). This se­quence number is dif­fer­ent from a mes­sage iden­ti­fier or Cor­rel­a­tion Iden­ti­fier. A mes­sage iden­ti­fier is a spe­cial at­trib­ute that uniquely iden­ti­fies each mes­sage. How­ever, in most cases, mes­sage iden­ti­fi­ers are not com­par­able; they are ba­sic­ally random values and often not even nu­meric. Even if they happen to have nu­mer­ical values, it is gen­er­ally a bad idea to over­load the se­quence number se­mantics over an ex­ist­ing mes­sage iden­ti­fier ele­ment. Cor­rel­a­tion Iden­ti­fi­ers are de­signed to match in­com­ing mes­sages to ori­ginal out­bound re­quests (see Re­quest-Reply ). The only re­quire­ment for Cor­rel­a­tion Iden­ti­fi­ers is unique­ness; they do not to have to be nu­meric or in se­quence. So, if we need to pre­serve the order of a series of mes­sages, we should define a sep­ar­ate field to track the po­s­i­tion of each mes­sage in the se­quence. Typ­ic­ally, this field can be part of the mes­sage header.

                                                                                                                                                                生成序列号可能比生成唯一标识符更耗时。通常,可以通过组合唯一位置信息(例如,NIC的MAC地址)和当前时间以分布式方式生成唯一标识符。大多数 GUID(全局唯一标识符)算法都是这样工作的。为了生成有序数字,我们通常需要一个计数器来在整个系统中分配数字。在大多数情况下,数字仅按升序排列是不够的,还需要连续。否则,将很难识别丢失的消息。如果我们不小心,这个序列号生成器很容易成为消息流的瓶颈。如果各个消息是使用Splitter,最好将编号权合并到Splitter。[ EAA ] 中的身份字段模式包含有关如何生成密钥和序列号的有用讨论。

                                                                                                                                                                Gen­er­at­ing se­quence num­bers can be more time con­sum­ing than gen­er­at­ing unique iden­ti­fi­ers. Often, unique iden­ti­fi­ers can be gen­er­ated in a dis­trib­uted fash­ion by com­bin­ing unique loc­a­tion in­form­a­tion (e.g., the MAC ad­dress of the NIC) and cur­rent time. Most GUID (glob­ally unique iden­ti­fier) al­gorithms work this way. To gen­er­ate in-se­quence num­bers, we gen­er­ally need a single counter that as­signs num­bers across the system. In most cases, it is not suf­fi­cient for the num­bers to be simply in as­cend­ing order, but they need to be con­sec­ut­ive as well. Oth­er­wise, it will be dif­fi­cult to identify miss­ing mes­sages. If we are not care­ful, this se­quence number gen­er­ator could easily become a bot­tle­neck for the mes­sage flow. If the in­di­vidual mes­sages are the result of using a Split­ter, it is best to in­cor­por­ate the num­ber­ing right into the Split­ter. The Iden­tity Field pat­tern in [EAA] con­tains a useful dis­cus­sion on how to gen­er­ate keys and se­quence num­bers.

                                                                                                                                                                内部运作

                                                                                                                                                                In­ternal Op­er­a­tion

                                                                                                                                                                序列号确保重排序器可以检测到不按顺序到达的消息。但是,当乱序消息到达时,重排序器应该做什么呢?失序消息意味着具有较高序列号的消息在具有较低序列号的消息之前到达。重排序器必须存储具有较高序列号的消息,直到它接收到具有较低序列号的所有“丢失”消息。同时,它还可能收到其他乱序消息,这些消息也必须被存储。一旦缓冲区包含连续的消息序列,重定序器将此序列发送到输出通道,然后从缓冲区中删除发送的消息(见图)。

                                                                                                                                                                Se­quence num­bers ensure that the Resequen­cer can detect mes­sages ar­riv­ing out of se­quence. But what should the Resequen­cer do when an out-of-se­quence mes­sage ar­rives? An out-of-se­quence mes­sage im­plies that a mes­sage with a higher se­quence number ar­rives before a mes­sage with a lower se­quence number. The Resequen­cer has to store the mes­sage with the higher se­quence number until it re­ceives all the "miss­ing" mes­sages with lower se­quence num­bers. Mean­while, it may re­ceive other out-of-se­quence mes­sages as well, which also have to be stored. Once the buffer con­tains a con­sec­ut­ive se­quence of mes­sages, the Resequen­cer sends this se­quence to the output chan­nel and then re­moves the sent mes­sages from the buffer (see figure).

                                                                                                                                                                重排序器的内部操作

                                                                                                                                                                In­ternal Op­er­a­tion of the Resequen­cer

                                                                                                                                                                图形/07inf24.gif

                                                                                                                                                                在这个简单的示例中,重排序器接收序列号为 1、3、5 和 2 的消息。我们假设序列以 1 开头,因此第一条消息可以立即发送并从缓冲区中删除。下一条消息的序列号为 3,因此我们丢失了消息 2。因此,我们存储消息 3,直到获得正确的消息序列。我们对下一条消息执行相同的操作,该消息的序列号为 5。一旦消息 2 进入,缓冲区将包含消息 2 和 3 的正确序列。因此,重排序器会发布这些消息并将它们从缓冲区中删除。 消息 5 保留在缓冲区中,直到序列中剩余的“间隙”被关闭。

                                                                                                                                                                In this simple ex­ample, the Resequen­cer re­ceives mes­sages with the se­quence num­bers 1, 3, 5, and 2. We assume that the se­quence starts with 1, so the first mes­sage can be sent right away and re­moved from the buffer. The next mes­sage has the se­quence number 3, so we are miss­ing mes­sage 2. There­fore, we store mes­sage 3 until we have a proper se­quence of mes­sages. We do the same with the next mes­sage, which has a se­quence number of 5. Once mes­sage 2 comes in, the buffer con­tains a proper se­quence of the mes­sages 2 and 3. There­fore, the Resequen­cer pub­lishes these mes­sages and re­moves them from the buffer. Mes­sage 5 re­mains in the buffer until the re­main­ing "gap" in the se­quence is closed.

                                                                                                                                                                避免缓冲区溢出

                                                                                                                                                                Avoid­ing Buffer Over­run

                                                                                                                                                                缓冲区应该有多大?如果我们正在处理很长的消息流,缓冲区可能会变得相当大。更糟糕的是,假设我们有一个包含多个处理单元的配置,每个处理单元处理特定的消息类型。如果一个处理单元发生故障,我们将收到一长串无序消息。缓冲区溢出几乎是肯定的。在某些情况下,我们可以使用消息队列来吸收待处理的消息。仅当消息传递基础设施允许我们根据选择标准从队列中读取消息而不是总是先读取最旧的消息时,此方法才有效。这样,我们可以轮询队列并查看第一个丢失的消息是否已进入,而无需消耗其间的所有消息。但在某些时候,

                                                                                                                                                                How big should the buffer be? If we are deal­ing with a long stream of mes­sages, the buffer can get rather large. Worse yet, let's assume we have a con­fig­ur­a­tion with mul­tiple pro­cess­ing units, each of which deals with a spe­cific mes­sage type. If one pro­cess­ing unit fails, we will get a long stream of out-of-se­quence mes­sages. A buffer over­run is almost cer­tain. In some cases, we can use the mes­sage queue to absorb the pending mes­sages. This works only if the mes­saging in­fra­struc­ture allows us to read mes­sages from the queue based on se­lec­tion cri­teria as op­posed to always read­ing the oldest mes­sage first. That way, we can poll the queue and see whether the first miss­ing mes­sage has come in yet without con­sum­ing all the mes­sages in between. At some point, though, even the stor­age al­loc­ated to the mes­sage queue will fill up.

                                                                                                                                                                避免缓冲区溢出的一种可靠方法是使用主动确认来限制消息生成器(见图)。

                                                                                                                                                                One robust way to avoid buffer over­runs is to throttle the mes­sage pro­du­cer by using active ac­know­ledg­ment (see figure).

                                                                                                                                                                主动确认避免缓冲区溢出

                                                                                                                                                                Active Ac­know­ledg­ment Avoids Buffer Over­flow

                                                                                                                                                                图形/07inf25.gif

                                                                                                                                                                正如我们之前讨论的,一次只发送一条消息是非常低效的。我们需要比这更聪明一点。更有效的方法是重排序器告诉生产者其缓冲区中有多少个可用槽。然后,消息节流器可以触发那么多消息,因为即使它们完全乱序,重排序器也能够将所有消息保存在缓冲区中并对它们重新排序。这种方法在效率和缓冲要求之间提供了良好的折衷。但是,它确实要求我们能够访问原始的按顺序消息流,以便插入发送缓冲区和限制。

                                                                                                                                                                As we dis­cussed earlier, send­ing only a single mes­sage at a time is very in­ef­fi­cient. We need to be a little smarter than that. A more ef­fi­cient way is for the Resequen­cer to tell the pro­du­cer how many slots it has avail­able in its buffer. The mes­sage throttle can then fire off that many mes­sages, since even if they get com­pletely out of order, the Resequen­cer will be able to hold all of them in the buffer and re-se­quence them. This ap­proach presents a good com­prom­ise between ef­fi­ciency and buffer re­quire­ments. How­ever, it does re­quire that we have access to the ori­ginal in-se­quence mes­sage stream in order to insert the send buffer and throttle.

                                                                                                                                                                这种方法与 TCP/IP 网络协议的工作方式非常相似。TCP 协议的关键功能之一是确保数据包通过网络按顺序传送。实际上,每个数据包可能会通过不同的网络路径进行路由,因此乱序数据包的出现相当频繁。接收器维护一个用作滑动窗口的循环缓冲区。接收方和发送方在每次确认之前协商要发送的数据包数量。由于发送方等待接收方的确认,因此快速发送方无法超过接收方或导致缓冲区溢出。特定的规则还可以防止所谓的愚蠢窗口综合症,即发送者和接收者可能陷入效率非常低的、一次一个数据包的模式。

                                                                                                                                                                This ap­proach is very sim­ilar to the way the TCP/IP net­work pro­tocol works. One of the key fea­tures of the TCP pro­tocol is to ensure in-se­quence de­liv­ery of pack­ets over the net­work. In real­ity, each packet may be routed through a dif­fer­ent net­work path, so out-of-se­quence pack­ets occur quite fre­quently. The re­ceiver main­tains a cir­cu­lar buffer that is used as a slid­ing window. Re­ceiver and sender ne­go­ti­ate on the number of pack­ets to send before each ac­know­ledg­ment. Be­cause the sender waits for an ac­know­ledg­ment from the re­ceiver, a fast sender cannot out­pace the re­ceiver or cause the buffer to over­flow. Spe­cific rules also pre­vent the so-called Silly Window Syn­drome, where sender and re­ceiver could fall into a very in­ef­fi­cient, one-packet-at-a-time mode.

                                                                                                                                                                缓冲区溢出问题的另一个解决方案是计算丢失消息的替代消息。如果接收者能够容忍“足够好”的消息数据并且不需要每条消息的精确数据或者如果速度比准确性更重要,则这种方法有效。例如,在 IP 语音传输中,填充空白数据包比对丢失的数据包发出重新请求会带来更好的用户体验,后者会导致语音流出现明显的延迟。

                                                                                                                                                                An­other solu­tion to the buffer over­run prob­lem is to com­pute stand-in mes­sages for the miss­ing mes­sage. This works if the re­cip­i­ent is tol­er­ant toward "good enough" mes­sage data and does not re­quire pre­cise data for each mes­sage or if speed is more im­port­ant than ac­cur­acy. For ex­ample, in voice over IP trans­mis­sions, filling in a blank packet res­ults in a better user ex­per­i­ence than is­su­ing a re-re­quest for a lost packet, which would cause a no­tice­able delay in the voice stream.

                                                                                                                                                                我们大多数应用程序开发人员都认为可靠的网络通信是理所当然的。在设计消息传递解决方案时,研究 TCP 的一些内部结构实际上是有帮助的,因为在其核心,IP 流量是异步且不可靠的,并且必须处理许多与企业集成解决方案相同的问题。有关 IP 协议的彻底处理,请参阅 [ Stevens ] 和 [ Wright ]。

                                                                                                                                                                Most of us ap­plic­a­tion de­ve­lopers take re­li­able net­work com­mu­nic­a­tion for gran­ted. When design­ing mes­saging solu­tions, it is ac­tu­ally help­ful to look into some of the in­tern­als of TCP, be­cause at its core, IP traffic is asyn­chron­ous and un­re­li­able and has to deal with many of the same issues en­ter­prise in­teg­ra­tion solu­tions do. For a thor­ough treat­ment of IP pro­to­cols see [Stevens] and [Wright].

                                                                                                                                                                示例: Microsoft .NET 中使用 MSMQ 的重排序器

                                                                                                                                                                Ex­ample: Resequen­cer in Mi­crosoft .NET with MSMQ

                                                                                                                                                                为了在现实场景中演示重排序器的功能,我们使用以下设置:

                                                                                                                                                                To demon­strate the func­tion of a Resequen­cer in a real-life scen­ario, we use the fol­low­ing setup:

                                                                                                                                                                重排序器测试配置

                                                                                                                                                                Resequen­cer Test Con­fig­ur­a­tion

                                                                                                                                                                图形/07inf26.gif

                                                                                                                                                                测试设置由四个主要组件组成,每个组件都作为 C# 类实现。这些组件通过消息队列服务提供的 MSMQ 消息队列进行通信,该服务是 Windows 2000 和 Windows XP 的一部分。

                                                                                                                                                                The test setup con­sists of four main com­pon­ents, each im­ple­men­ted as a C# class. The com­pon­ents com­mu­nic­ate via MSMQ mes­sage queues provided by the Mes­sage Queuing ser­vice that is part of Win­dows 2000 and Win­dows XP.

                                                                                                                                                                1. MQSend充当测试消息生成器。每条消息的消息正文都包含一个简单的文本字符串。MQSend还在每条消息的AppSpecific属性内为每条消息配备一个序列号。序列从1开始,消息数量可以从命令行传入。MQSend将消息发布到专用队列inQueue

                                                                                                                                                                2. MQSend acts as the Test Mes­sage gen­er­ator. The mes­sage body for each mes­sage con­tains a simple text string. MQSend also equips each mes­sage with a se­quence number inside the AppSpe­cific prop­erty of each mes­sage. The se­quence starts with 1, and the number of mes­sages can be passed in from the com­mand line. MQSend pub­lishes the mes­sages to the private queue in­Queue.

                                                                                                                                                                3. DelayProcessorinQueue 读取消息。唯一的“处理”包括将相同消息重新发布到 outQueue 之前的定时延迟。 我们并行使用三个DelayProcessor来模拟负载平衡的处理单元。处理器充当竞争消费者,因此每条消息都由一个处理器使用。所有处理器都将消息发布到outQueue 。由于处理速度不同,outQueue 上的消息是乱序的。

                                                                                                                                                                4. DelayPro­cessor reads mes­sages off in­Queue. The only "pro­cess­ing" con­sists of a timed delay before the identical mes­sage is re­pub­lished to out­Queue. We use three DelayPro­cessors in par­al­lel to sim­u­late a load-bal­anced pro­cess­ing unit. The pro­cessors act as Com­pet­ing Con­sumers, so each mes­sage is con­sumed by ex­actly one pro­cessor. All pro­cessors pub­lish mes­sages to out­Queue. Be­cause of the dif­fer­ent pro­cess­ing speeds, mes­sages on out­Queue are out of se­quence.

                                                                                                                                                                5. Resequencer缓冲传入的失序消息,并将它们按顺序重新发布到equenceQueue

                                                                                                                                                                6. The Resequen­cer buf­fers in­com­ing out-of-se­quence mes­sages and re­pub­lishes them in se­quence to the se­quenceQueue.

                                                                                                                                                                7. MQSequenceReceive 从sequenceQueue 中读取消息验证AppSpecific 属性中的序列号是否按升序排列。

                                                                                                                                                                8. MQSequen­ceRe­ceive reads mes­sages off the se­quenceQueue and veri­fies that the se­quence num­bers in the AppSpe­cific prop­erty are in as­cend­ing order.

                                                                                                                                                                如果我们启动所有组件,我们会看到类似于下图的调试输出。从处理器输出窗口的大小,我们可以看到处理器工作的不同速度。正如预期的那样,到达重排序器的消息不按顺序排列(在本次运行中,到达的消息为 3、4、1、5、7、2...)。我们可以从重排序器输出中看到,如果消息丢失,重排序器如何缓冲传入消息。一旦丢失的消息到达,重排序器就会以正确的顺序发布现已完成的序列。

                                                                                                                                                                If we fire up all com­pon­ents, we see debug output sim­ilar to the fol­low­ing figure. From the size of the pro­cessor output win­dows, we can see the dif­fer­ent speeds at which the pro­cessors are work­ing. As ex­pec­ted, the mes­sages ar­riv­ing at the Resequen­cer are not in se­quence (in this run, the mes­sages ar­rived as 3, 4, 1, 5, 7, 2, ...). We can see from the Resequen­cer output how the Resequen­cer buf­fers the in­com­ing mes­sages if a mes­sage is miss­ing. As soon as the miss­ing mes­sage ar­rives, the Resequen­cer pub­lishes the now com­pleted se­quence in the cor­rect order.

                                                                                                                                                                测试组件的输出

                                                                                                                                                                Output from the Test Com­pon­ents

                                                                                                                                                                图形/07inf27.gif

                                                                                                                                                                查看测试设置,我们意识到 DelayProcessor和Resequencer 有一些共同点:它们都从输入队列读取消息并将其发布到输出队列。唯一的区别在于消息的实际处理之间发生的情况。因此,我们创建了一个公共基类来封装此通用过滤器的基本功能(请参阅管道和过滤器)。它包含用于队列创建以及异步接收、处理和发送消息的便捷方法和模板方法。我们称这个基类为Processor(见图)。

                                                                                                                                                                Look­ing at the test setup, we real­ize that both the DelayPro­cessor and the Resequen­cer have a few things in common: They both read mes­sages from an input queue and pub­lish them to an output queue. The only dif­fer­ence is in what hap­pens in betweenthe actual pro­cess­ing of the mes­sage. There­fore, we cre­ated a common base class that en­cap­su­lates the basic func­tion­al­ity of this gen­eric filter (see Pipes and Fil­ters ). It con­tains con­veni­ence and tem­plate meth­ods for queue cre­ation and asyn­chron­ous re­ceiv­ing, pro­cess­ing, and send­ing of mes­sages. We call this base class Pro­cessor (see figure).

                                                                                                                                                                DelayProcessor 和 Resequencer 都继承自 Common Processor 类

                                                                                                                                                                Both the DelayPro­cessor and the Resequen­cer In­herit from the Common Pro­cessor Class

                                                                                                                                                                图形/07inf28.gif

                                                                                                                                                                处理器的默认实现只是将消息从输入队列复制到输出队列。要实现Resequencer ,我们必须重写ProcessMessage。对于 Resequencer 来说processMessage方法将接收到的消息添加到缓冲区中,该缓冲区以Hashtable 的。缓冲区中的消息由消息序列号作为键控,该消息序列号存储在AppSpecific属性中。添加新消息后,方法SendConsecutiveMessages检查我们是否有从下一个未完成消息开始的连续序列。如果是,该方法将发送所有连续消息并将它们从缓冲区中删除。

                                                                                                                                                                The de­fault im­ple­ment­a­tion of the Pro­cessor simply copies mes­sages from the input queue to the output queue. To im­ple­ment the Resequen­cer, we have to over­ride the de­fault im­ple­ment­a­tion of the Pro­cess­Mes­sage method. In the case of the Resequen­cer, the pro­cess­Mes­sage method adds the re­ceived mes­sage in the buffer, which is im­ple­men­ted as a Hasht­able. The mes­sages in the buffer are keyed by the mes­sage se­quence number, which is stored in the AppSpe­cific prop­erty. Once the new mes­sage is added, the method Send­Con­sec­ut­iveMes­sages checks whether we have a con­sec­ut­ive se­quence start­ing with the next out­stand­ing mes­sages. If so, the method sends all con­sec­ut­ive mes­sages and re­moves them from the buffer.



                                                                                                                                                                重排序器.cs
                                                                                                                                                                使用系统;
                                                                                                                                                                使用系统消息传递;
                                                                                                                                                                使用系统集合;
                                                                                                                                                                使用消息处理器;
                                                                                                                                                                
                                                                                                                                                                命名空间重排序器
                                                                                                                                                                {
                                                                                                                                                                
                                                                                                                                                                    重排序器类:处理器
                                                                                                                                                                    {
                                                                                                                                                                        私有 int 起始索引 = 1;
                                                                                                                                                                        私有 IDictionary 缓冲区 = (IDictionary)(new Hashtable());
                                                                                                                                                                        私有 int endIndex = -1;
                                                                                                                                                                
                                                                                                                                                                        公共重新排序器(消息队列输入队列,消息队列输出队列)
                                                                                                                                                                                          :基(输入队列,输出队列){}
                                                                                                                                                                
                                                                                                                                                                        protected override void ProcessMessage(消息 m)
                                                                                                                                                                        {
                                                                                                                                                                            添加到缓冲区(m);
                                                                                                                                                                            发送连续消息();
                                                                                                                                                                        }
                                                                                                                                                                
                                                                                                                                                                        私有无效AddToBuffer(消息m)
                                                                                                                                                                        {
                                                                                                                                                                            Int32 msgIndex = m.AppSpecific;
                                                                                                                                                                            Console.WriteLine("收到的消息索引{0}", msgIndex);
                                                                                                                                                                            if (消息索引 < 开始索引)
                                                                                                                                                                            {
                                                                                                                                                                                Console.WriteLine("消息索引超出范围!当前起始位置为:{0}",
                                                                                                                                                                                                  开始索引);
                                                                                                                                                                            }
                                                                                                                                                                            别的
                                                                                                                                                                            {
                                                                                                                                                                                buffer.Add(msgIndex, m);
                                                                                                                                                                                if (消息索引 > 结束索引)
                                                                                                                                                                                    结束索引=消息索引;
                                                                                                                                                                            }
                                                                                                                                                                            Console.WriteLine("缓冲区范围:{0} - {1}", startIndex, endIndex);
                                                                                                                                                                        }
                                                                                                                                                                
                                                                                                                                                                        私有无效SendConsecutiveMessages()
                                                                                                                                                                        {
                                                                                                                                                                            while (buffer.Contains(startIndex))
                                                                                                                                                                            {
                                                                                                                                                                                消息 m = (消息)(缓冲区[startIndex]);
                                                                                                                                                                                Console.WriteLine("正在发送索引为{0}的消息", startIndex);
                                                                                                                                                                                输出队列.Send(m);
                                                                                                                                                                                缓冲区.删除(startIndex);
                                                                                                                                                                                开始索引++;
                                                                                                                                                                            }
                                                                                                                                                                        }
                                                                                                                                                                    }
                                                                                                                                                                
                                                                                                                                                                }
                                                                                                                                                                
                                                                                                                                                                using System;
                                                                                                                                                                using System.Mes­saging;
                                                                                                                                                                using System.Col­lec­tions;
                                                                                                                                                                using Ms­gPro­cessor;
                                                                                                                                                                
                                                                                                                                                                namespace Resequen­cer
                                                                                                                                                                {
                                                                                                                                                                
                                                                                                                                                                    class Resequen­cer : Pro­cessor
                                                                                                                                                                    {
                                                                                                                                                                        private int startIn­dex = 1;
                                                                                                                                                                        private IDic­tion­ary buffer = (IDic­tion­ary)(new Hasht­able());
                                                                                                                                                                        private int endIn­dex = -1;
                                                                                                                                                                
                                                                                                                                                                        public Resequen­cer(Mes­sageQueue in­putQueue, Mes­sageQueue out­putQueue)
                                                                                                                                                                                          : base (in­putQueue, out­putQueue) {}
                                                                                                                                                                
                                                                                                                                                                        pro­tec­ted over­ride void Pro­cess­Mes­sage(Mes­sage m)
                                                                                                                                                                        {
                                                                                                                                                                            Ad­dTo­Buf­fer(m);
                                                                                                                                                                            Send­Con­sec­ut­iveMes­sages();
                                                                                                                                                                        }
                                                                                                                                                                
                                                                                                                                                                        private void Ad­dTo­Buf­fer(Mes­sage m)
                                                                                                                                                                        {
                                                                                                                                                                            Int32 ms­gIn­dex = m.AppSpe­cific;
                                                                                                                                                                            Con­sole.WriteLine("Re­ceived mes­sage index {0}", ms­gIn­dex);
                                                                                                                                                                            if (ms­gIn­dex < startIn­dex)
                                                                                                                                                                            {
                                                                                                                                                                                Con­sole.WriteLine("Out of range mes­sage index! Cur­rent start is: {0}",
                                                                                                                                                                                                  startIn­dex);
                                                                                                                                                                            }
                                                                                                                                                                            else
                                                                                                                                                                            {
                                                                                                                                                                                buffer.Add(ms­gIn­dex, m);
                                                                                                                                                                                if (ms­gIn­dex > endIn­dex)
                                                                                                                                                                                    endIn­dex = ms­gIn­dex;
                                                                                                                                                                            }
                                                                                                                                                                            Con­sole.WriteLine("    Buffer range: {0} - {1}", startIn­dex, endIn­dex);
                                                                                                                                                                        }
                                                                                                                                                                
                                                                                                                                                                        private void Send­Con­sec­ut­iveMes­sages()
                                                                                                                                                                        {
                                                                                                                                                                            while (buffer.Con­tains(startIn­dex))
                                                                                                                                                                            {
                                                                                                                                                                                Mes­sage m = (Mes­sage)(buffer[startIn­dex]);
                                                                                                                                                                                Con­sole.WriteLine("Send­ing mes­sage with index {0}", startIn­dex);
                                                                                                                                                                                out­putQueue.Send(m);
                                                                                                                                                                                buffer.Remove(startIn­dex);
                                                                                                                                                                                startIn­dex++;
                                                                                                                                                                            }
                                                                                                                                                                        }
                                                                                                                                                                    }
                                                                                                                                                                
                                                                                                                                                                }
                                                                                                                                                                

                                                                                                                                                                正如您所看到的,重排序器假定消息序列从 1 开始。如果消息生产者也从 1 开始序列,并且两个组件在组件的生命周期内保持相同的序列,则这种方法效果很好。为了使重排序器更加灵活,消息生产者应在发送序列的第一条消息之前与重排序器协商序列起始号。此过程类似于 TCP 协议的连接序列期间交换的 SYN 消息(请参阅 [ Stevens ])。

                                                                                                                                                                As you can see, the Resequen­cer as­sumes that the mes­sage se­quence starts with 1. This works well if the mes­sage pro­du­cer also starts the se­quence from 1 and the two com­pon­ents main­tain the same se­quence over the life­time of the com­pon­ents. To make the Resequen­cer more flex­ible, the mes­sage pro­du­cer should ne­go­ti­ate a se­quence start number with the Resequen­cer before send­ing the first mes­sage of the se­quence. This pro­cess is ana­log­ous to the SYN mes­sages ex­changed during the con­nect se­quence of the TCP pro­tocol (see [Stevens]).

                                                                                                                                                                当前的实现也没有针对缓冲区溢出的规定。我们假设DelayProcessor中止或发生故障并吃掉了一条消息。重定序将无限期地等待丢失的消息,直到缓冲区溢出。在大容量场景中,消息和重定序器需要协商描述重定序器可以缓冲的最大消息数的窗口大小。一旦缓冲区已满,错误处理程序必须确定如何处理丢失的消息。例如,生产者可以重新发送消息,或者可以注入“虚拟”消息。

                                                                                                                                                                The cur­rent im­ple­ment­a­tion also has no pro­vi­sions for a buffer over­run. Let's assume a DelayPro­cessor aborts or mal­func­tions and eats a mes­sage. The Resequen­cer will wait in­def­in­itely for the missed mes­sage until the buffer over­flows. In high-volume scen­arios, the mes­sage and the Resequen­cer need to ne­go­ti­ate a window size de­scrib­ing the max­imum number of mes­sages the Resequen­cer can buffer. Once the buffer is full, an error hand­ler has to de­term­ine how to deal with the miss­ing mes­sage. For ex­ample, the pro­du­cer could resend the mes­sage, or a "dummy" mes­sage could be in­jec­ted.

                                                                                                                                                                Processor基类相对简单。它通过使用BeginReceive 和EndReceive方法来进行异步消息处理。由于很容易忘记在消息处理结束时调用BeginReceive ,因此我们使用了包含此步骤的模板方法。然后,子类可以重写ProcessMessage 方法,而不必担心异步处理。

                                                                                                                                                                The Pro­cessor base class is re­l­at­ively simple. It uses asyn­chron­ous mes­sage pro­cess­ing by using the Be­gin­Re­ceive and En­dRe­ceive meth­ods. Be­cause it is easy to forget to call Be­gin­Re­ceive at the end of the mes­sage pro­cess­ing, we used a tem­plate method that in­cor­por­ates this step. Sub­classes can then over­ride the Pro­cess­Mes­sage method without having to worry about the asyn­chron­ous pro­cess­ing.

                                                                                                                                                                处理器.cs
                                                                                                                                                                使用系统;
                                                                                                                                                                使用系统消息传递;
                                                                                                                                                                使用系统线程;
                                                                                                                                                                
                                                                                                                                                                命名空间消息处理器
                                                                                                                                                                {
                                                                                                                                                                    公共类处理器
                                                                                                                                                                    {
                                                                                                                                                                        受保护的消息队列输入队列;
                                                                                                                                                                        受保护的消息队列输出队列;
                                                                                                                                                                
                                                                                                                                                                        公共处理器(消息队列输入队列,消息队列输出队列)
                                                                                                                                                                        {
                                                                                                                                                                            this.inputQueue = inputQueue;
                                                                                                                                                                            this.outputQueue = 输出队列;
                                                                                                                                                                            inputQueue.Formatter = 新System.Messaging.XmlMessageFormatter
                                                                                                                                                                                                       (new String[] {"System.String,mscorlib"});
                                                                                                                                                                            inputQueue.MessageReadPropertyFilter.ClearAll();
                                                                                                                                                                            inputQueue.MessageReadPropertyFilter.AppSpecific = true;
                                                                                                                                                                            inputQueue.MessageReadPropertyFilter.Body = true;
                                                                                                                                                                            inputQueue.MessageReadPropertyFilter.CorrelationId = true;
                                                                                                                                                                            inputQueue.MessageReadPropertyFilter.Id = true;
                                                                                                                                                                            Console.WriteLine("正在处理来自 " + inputQueue.Path + 的消息
                                                                                                                                                                                              “到”+outputQueue.Path);
                                                                                                                                                                        }
                                                                                                                                                                
                                                                                                                                                                        公共无效进程()
                                                                                                                                                                        {
                                                                                                                                                                            inputQueue.ReceiveCompleted += 新
                                                                                                                                                                                ReceiveCompletedEventHandler(OnReceiveCompleted);
                                                                                                                                                                            inputQueue.BeginReceive();
                                                                                                                                                                        }
                                                                                                                                                                
                                                                                                                                                                        私人无效OnReceiveCompleted(对象源,
                                                                                                                                                                                                        ReceiveCompletedEventArgs asyncResult)
                                                                                                                                                                        {
                                                                                                                                                                            消息队列 mq = (消息队列)源;
                                                                                                                                                                
                                                                                                                                                                            消息 m = mq.EndReceive(asyncResult.AsyncResult);
                                                                                                                                                                            m.Formatter = 新 System.Messaging.XmlMessageFormatter
                                                                                                                                                                                               (new String[] {"System.String,mscorlib"});
                                                                                                                                                                
                                                                                                                                                                            处理消息(m);
                                                                                                                                                                
                                                                                                                                                                            mq.BeginReceive();
                                                                                                                                                                        }
                                                                                                                                                                
                                                                                                                                                                        protected virtual void ProcessMessage(消息 m)
                                                                                                                                                                        {
                                                                                                                                                                            字符串主体 = (字符串)m.Body;
                                                                                                                                                                            Console.WriteLine("收到消息:" + body);
                                                                                                                                                                            输出队列.Send(m);
                                                                                                                                                                        }
                                                                                                                                                                    }
                                                                                                                                                                }
                                                                                                                                                                
                                                                                                                                                                using System;
                                                                                                                                                                using System.Mes­saging;
                                                                                                                                                                using System.Thread­ing;
                                                                                                                                                                
                                                                                                                                                                namespace Ms­gPro­cessor
                                                                                                                                                                {
                                                                                                                                                                    public class Pro­cessor
                                                                                                                                                                    {
                                                                                                                                                                        pro­tec­ted Mes­sageQueue in­putQueue;
                                                                                                                                                                        pro­tec­ted Mes­sageQueue out­putQueue;
                                                                                                                                                                
                                                                                                                                                                        public Pro­cessor (Mes­sageQueue in­putQueue, Mes­sageQueue out­putQueue)
                                                                                                                                                                        {
                                                                                                                                                                            this.in­putQueue = in­putQueue;
                                                                                                                                                                            this.out­putQueue = out­putQueue;
                                                                                                                                                                            in­putQueue.Format­ter = new System.Mes­saging.Xm­lMes­sage­Format­ter
                                                                                                                                                                                                       (new String[] {"System.String,mscorlib"});
                                                                                                                                                                            in­putQueue.Mes­sageRead­Prop­er­ty­Fil­ter.Clear­All();
                                                                                                                                                                            in­putQueue.Mes­sageRead­Prop­er­ty­Fil­ter.AppSpe­cific = true;
                                                                                                                                                                            in­putQueue.Mes­sageRead­Prop­er­ty­Fil­ter.Body = true;
                                                                                                                                                                            in­putQueue.Mes­sageRead­Prop­er­ty­Fil­ter.Cor­rel­a­tionId = true;
                                                                                                                                                                            in­putQueue.Mes­sageRead­Prop­er­ty­Fil­ter.Id = true;
                                                                                                                                                                            Con­sole.WriteLine("Pro­cess­ing mes­sages from " + in­putQueue.Path +
                                                                                                                                                                                              " to " + out­putQueue.Path);
                                                                                                                                                                        }
                                                                                                                                                                
                                                                                                                                                                        public void Pro­cess()
                                                                                                                                                                        {
                                                                                                                                                                            in­putQueue.Re­ceive­Com­pleted += new
                                                                                                                                                                                Re­ceive­Com­plete­dE­ventHand­ler(On­Re­ceive­Com­pleted);
                                                                                                                                                                            in­putQueue.Be­gin­Re­ceive();
                                                                                                                                                                        }
                                                                                                                                                                
                                                                                                                                                                        private void On­Re­ceive­Com­pleted(Object source,
                                                                                                                                                                                                        Re­ceive­Com­plete­dEvent­Args asyn­cRes­ult)
                                                                                                                                                                        {
                                                                                                                                                                            Mes­sageQueue mq = (Mes­sageQueue)source;
                                                                                                                                                                
                                                                                                                                                                            Mes­sage m = mq.En­dRe­ceive(asyn­cRes­ult.Asyn­cRes­ult);
                                                                                                                                                                            m.Format­ter =  new System.Mes­saging.Xm­lMes­sage­Format­ter
                                                                                                                                                                                               (new String[] {"System.String,mscorlib"});
                                                                                                                                                                
                                                                                                                                                                            Pro­cess­Mes­sage(m);
                                                                                                                                                                
                                                                                                                                                                            mq.Be­gin­Re­ceive();
                                                                                                                                                                        }
                                                                                                                                                                
                                                                                                                                                                        pro­tec­ted vir­tual void Pro­cess­Mes­sage(Mes­sage m)
                                                                                                                                                                        {
                                                                                                                                                                            string body = (string)m.Body;
                                                                                                                                                                            Con­sole.WriteLine("Re­ceived Mes­sage: " + body);
                                                                                                                                                                            out­putQueue.Send(m);
                                                                                                                                                                        }
                                                                                                                                                                    }
                                                                                                                                                                }
                                                                                                                                                                

                                                                                                                                                                  组合消息处理器

                                                                                                                                                                  Composed Message Processor

                                                                                                                                                                  图形/composedmessage_icon.gif

                                                                                                                                                                  基于内容的路由器拆分器模式中提供的订单处理示例处理由各个行项目组成的传入订单。每个行项目都需要使用相应的库存系统进行库存检查。验证所有商品后,我们希望将验证后的订单消息传递到下一个处理步骤。

                                                                                                                                                                  The order-pro­cess­ing ex­ample presen­ted in the Con­tent-Based Router and Split­ter pat­terns pro­cesses an in­com­ing order con­sist­ing of in­di­vidual line items. Each line item re­quires an in­vent­ory check with the re­spect­ive in­vent­ory system. After all items have been veri­fied, we want to pass the val­id­ated order mes­sage to the next pro­cess­ing step.

                                                                                                                                                                  当处理由多个元素组成的消息(每个元素可能需要不同的处理)时,如何维护整体消息流?

                                                                                                                                                                  How can you main­tain the over­all mes­sage flow when pro­cess­ing a mes­sage con­sist­ing of mul­tiple ele­ments, each of which may re­quire dif­fer­ent pro­cess­ing?



                                                                                                                                                                  这个问题似乎包含我们已经定义的多种模式的元素。拆分可以将单个消息拆分为多个部分。然后,基于内容的路由器可以根据消息内容或类型通过正确的处理步骤路由各个子消息。管道和过滤器架构风格允许我们将这两种模式链接在一起,以便我们可以将组合消息中的每个项目路由到适当的处理步骤:

                                                                                                                                                                  This prob­lem seems to con­tain ele­ments of mul­tiple pat­terns we have already defined. A Split­ter can split a single mes­sage into mul­tiple parts. A Con­tent-Based Router could then route in­di­vidual submes­sages through the cor­rect pro­cess­ing steps based on mes­sage con­tent or type. The Pipes and Fil­ters ar­chi­tec­tural style allows us to chain to­gether these two pat­terns so that we can route each item in the com­posed mes­sage to the ap­pro­pri­ate pro­cess­ing steps:

                                                                                                                                                                  组合分配器和路由器

                                                                                                                                                                  Com­bin­ing a Split­ter and a Router

                                                                                                                                                                  图形/07inf29.gif

                                                                                                                                                                  在我们的示例中,这意味着每个订单商品都会被路由到正确的库存系统进行验证。库存系统彼此解耦,每个系统仅接收其可以处理的物品。

                                                                                                                                                                  In our ex­ample, this means that each order item is routed to the proper in­vent­ory system to be veri­fied. The in­vent­ory sys­tems are de­coupled from each other, and each system re­ceives only items that can be pro­cessed by it.

                                                                                                                                                                  到目前为止,该设置的缺点是我们无法确定所有已订购的商品是否实际上都有库存并且可以发货。我们还需要检索所有商品的价格(考虑批量折扣)并将它们组合成一张发票。这要求我们继续处理,就像订单仍然是单个消息一样,即使我们只是将其分成许多子消息。

                                                                                                                                                                  The short­com­ing of the setup so far is that we cannot find out whether all items that have been ordered are ac­tu­ally in stock and can be shipped. We also need to re­trieve the prices for all items (factor­ing volume dis­counts) and as­semble them into a single in­voice. This re­quires us to con­tinue pro­cess­ing as if the order is still a single mes­sage even though we just chopped it up into many submes­sages.

                                                                                                                                                                  一种方法是将通过特定库存系统的所有物品重新组装成单独的订单。从此时起,该订单可以作为一个整体进行处理:可以履行并发货订单,并且可以发送账单。每个子订单都被视为一个独立的进程。在某些情况下,缺乏对下游过程的控制可能使这种方法成为唯一可用的解决方案。例如,亚马逊对其销售的大部分商品都采用这种方法。订单被发送到不同的履行中心并从那里进行管理。

                                                                                                                                                                  One ap­proach would be to just re­as­semble all those items that pass through a spe­cific in­vent­ory system into a sep­ar­ate order. This order can be pro­cessed as a whole from this point on: The order can be ful­filled and shipped, and a bill can be sent. Each sub­or­der is treated as an in­de­pend­ent pro­cess. In some in­stances, lack of con­trol over the down­stream pro­cess may make this ap­proach the only avail­able solu­tion. For ex­ample, Amazon fol­lows this ap­proach for a large por­tion of the goods it sells. Orders are routed to dif­fer­ent ful­fill­ment houses and man­aged from there.

                                                                                                                                                                  但是,这种方法可能无法提供最佳的客户体验。客户可能会收到多于一批货物和多于一张发票。退货或争议可能难以解决。对于订购书籍的消费者来说,这不是一个大问题,但如果各个订购项目相互依赖,则可能会很困难。我们假设订单包含构成搁架系统的家具物品。客户不会高兴地收到许多装有家具元件的大盒子,只是发现所需的安装硬件暂时无法提供,并将在稍后发货。

                                                                                                                                                                  How­ever, this ap­proach may not provide the best cus­tomer ex­per­i­ence. The cus­tomer may re­ceive more than one ship­ment and more than one in­voice. Re­turns or dis­putes may be dif­fi­cult to ac­com­mod­ate. This is not a big issue with con­sumers or­der­ing books, but may prove dif­fi­cult if in­di­vidual order items depend on each other. Let's assume that the order con­sists of fur­niture items that make up a shelving system. The cus­tomer would not be pleased to re­ceive a number of huge boxes con­tain­ing fur­niture ele­ments just to find out that the re­quired mount­ing hard­ware is tem­por­ar­ily un­avail­able and will be shipped at a later time.

                                                                                                                                                                  消息传递系统的异步特性使得任务分配比同步方法调用更加复杂。我们可以发送每个单独的订单项目并等待响应返回,然后再检查下一个项目。这将简化时间依赖性,但会使系统效率非常低。我们希望利用每个系统可以同时处理多个订单的优势。

                                                                                                                                                                  The asyn­chron­ous nature of a mes­saging system makes dis­tri­bu­tion of tasks more com­plic­ated than syn­chron­ous method calls. We could dis­patch each in­di­vidual order item and wait for a re­sponse to come back before we check the next item. This would sim­plify the tem­poral de­pend­en­cies but would make the system very in­ef­fi­cient. We would like to take ad­vant­age of the fact that each system can pro­cess mul­tiple orders sim­ul­tan­eously.

                                                                                                                                                                  使用组合消息处理器来处理组合消息。组合消息处理器将消息拆分,将子消息路由到适当的目的地,并将响应重新聚合回单个消息。

                                                                                                                                                                  Use a Com­posed Mes­sage Pro­cessor to pro­cess a com­pos­ite mes­sage. The Com­posed Mes­sage Pro­cessor splits the mes­sage up, routes the submes­sages to the ap­pro­pri­ate des­tin­a­tions, and re­ag­greg­ates the re­sponses back into a single mes­sage.

                                                                                                                                                                  图形/07inf30.gif



                                                                                                                                                                  组合消息处理器使用聚合器来协调分派到多个库存系统的请求。每个处理单元向聚合器发送一条响应消息,说明指定项目的现有库存。聚合收集各个响应并根据预定义的算法对其进行处理。

                                                                                                                                                                  The Com­posed Mes­sage Pro­cessor uses an Ag­greg­ator to re­con­cile the re­quests that were dis­patched to the mul­tiple in­vent­ory sys­tems. Each pro­cess­ing unit sends a re­sponse mes­sage to the Ag­greg­ator stat­ing the in­vent­ory on hand for the spe­cified item. The Ag­greg­ator col­lects the in­di­vidual re­sponses and pro­cesses them based on a pre­defined al­gorithm.

                                                                                                                                                                  由于所有子消息都源自一条消息,因此我们可以将附加信息(例如子消息的数量)传递给聚合器,以定义更有效的聚合策略。尽管如此,组合消息处理器仍然需要处理消息丢失或延迟的问题。如果库存系统不可用,我们是否要延迟处理包含该系统中的商品的所有订单?或者我们应该将它们路由到异常队列以供人工手动评估?如果缺少单个响应,我们是否应该重新发送库存请求消息?有关这些权衡的更详细讨论,请参阅聚合器

                                                                                                                                                                  Be­cause all submes­sages ori­gin­ate from a single mes­sage, we can pass ad­di­tional in­form­a­tion, such as the number of submes­sages, to the Ag­greg­ator to define a more ef­fi­cient ag­greg­a­tion strategy. Nev­er­the­less, the Com­posed Mes­sage Pro­cessor still has to deal with issues around miss­ing or delayed mes­sages. If an in­vent­ory system is un­avail­able, do we want to delay pro­cess­ing of all orders that in­clude items from that system? Or should we route them to an ex­cep­tion queue for a human to eval­u­ate manu­ally? If a single re­sponse is miss­ing, should we resend the in­vent­ory re­quest mes­sage? For a more de­tailed dis­cus­sion of these trade-offs, see Ag­greg­ator.

                                                                                                                                                                  该模式演示了如何将多个单独的模式组合成一个更大的模式。对于系统的其余部分,组合消息处理器看起来就像一个具有单个输入通道和单个输出通道的简单过滤器。因此,它提供了更复杂的内部工作的有效抽象。

                                                                                                                                                                  This pat­tern demon­strates how sev­eral in­di­vidual pat­terns can be com­posed into a single larger pat­tern. To the rest of the system, the Com­posed Mes­sage Pro­cessor ap­pears like a simple filter with a single input chan­nel and a single output chan­nel. As such, it provides an ef­fect­ive ab­strac­tion of the more com­plex in­ternal work­ings.

                                                                                                                                                                  作为单个过滤器的复合消息处理器

                                                                                                                                                                  The Com­pos­ite Mes­sage Pro­cessor as a Single Filter

                                                                                                                                                                  图形/07inf31.gif

                                                                                                                                                                    分散-聚集

                                                                                                                                                                    Scatter-Gather

                                                                                                                                                                    在前面模式中介绍的订单处理示例中,当前没有库存的每个订单项目可以由多个外部供应商之一提供。然而,供应商可能有也可能没有相应产品的库存,他们可能会收取不同的价格,或者他们可能能够在不同的日期之前供应零件。为了以尽可能最好的方式完成订单,我们应该向所有供应商请求报价,并决定哪一个供应商为我们所请求的项目提供了最佳条款。

                                                                                                                                                                    In the order-pro­cess­ing ex­ample in­tro­duced in the pre­vi­ous pat­terns, each order item that is not cur­rently in stock could be sup­plied by one of mul­tiple ex­ternal sup­pli­ers. How­ever, the sup­pli­ers may or may not have the re­spect­ive item in stock, they may charge a dif­fer­ent price, or they may be able to supply the part by a dif­fer­ent date. To fill the order in the best way pos­sible, we should re­quest quotes from all sup­pli­ers and decide which one provides us with the best term for the re­ques­ted item.

                                                                                                                                                                    当消息必须发送给多个收件人且每个收件人都可以发送回复时,如何维护整体消息流?

                                                                                                                                                                    How do you main­tain the over­all mes­sage flow when a mes­sage must be sent to mul­tiple re­cip­i­ents, each of which may send a reply?



                                                                                                                                                                    该解决方案应允许灵活地确定消息的收件人。我们可以集中确定批准的供应商名单,也可以让任何感兴趣的供应商参与投标。由于我们对收件人没有(或很少)控制,因此我们必须准备好接收部分(但不是全部)收件人的回复。对投标规则的此类更改不应影响解决方案的结构完整性。

                                                                                                                                                                    The solu­tion should allow for flex­ib­il­ity in de­term­in­ing the re­cip­i­ents of the mes­sage. We can either de­term­ine the list of ap­proved sup­pli­ers cent­rally or we can let any in­ter­ested sup­plier par­ti­cip­ate in the bid. Since we have no (or little) con­trol over the re­cip­i­ents, we must be pre­pared to re­ceive re­sponses from some, but not all, re­cip­i­ents. Such changes to the bid­ding rules should not impact the struc­tural in­teg­rity of the solu­tion.

                                                                                                                                                                    该解决方案应在任何后续处理中隐藏各个接收者的号码和身份。在本地封装消息的分发可以使其他组件独立于各个消息的路由。

                                                                                                                                                                    The solu­tion should hide the number and iden­tity of the in­di­vidual re­cip­i­ents from any sub­se­quent pro­cess­ing. En­cap­su­lat­ing the dis­tri­bu­tion of the mes­sage loc­ally keeps other com­pon­ents in­de­pend­ent from the route of the in­di­vidual mes­sages.

                                                                                                                                                                    我们还需要协调后续的消息流。最简单的解决方案可能是每个收件人将回复发布到频道,并让后续组件处理各个消息的解决方案。然而,这将要求后续组件知道消息被发送给多个收件人。对于后续组件来说,在不了解已应用的路由逻辑的情况下处理各个消息也可能会更加困难。

                                                                                                                                                                    We also need to co­ordin­ate the sub­se­quent mes­sage flow. The easi­est solu­tion might be for each re­cip­i­ent to post the reply to a chan­nel and let sub­se­quent com­pon­ents deal with the res­ol­u­tion of the in­di­vidual mes­sages. How­ever, this would re­quire sub­se­quent com­pon­ents to be aware of the mes­sage being sent to mul­tiple re­cip­i­ents. It might also be harder for sub­se­quent com­pon­ents to pro­cess the in­di­vidual mes­sages without having any know­ledge of the rout­ing logic that has been ap­plied.

                                                                                                                                                                    将路由逻辑、收件人和各个消息的后处理合并到一个逻辑组件中是有意义的。

                                                                                                                                                                    It makes sense to com­bine the rout­ing logic, the re­cip­i­ents, and the post­pro­cess­ing of the in­di­vidual mes­sages into one lo­gical com­pon­ent.

                                                                                                                                                                    使用Scatter-Gather将消息广播给多个收件人并将响应重新聚合回单个消息。

                                                                                                                                                                    Use a Scat­ter-Gather that broad­casts a mes­sage to mul­tiple re­cip­i­ents and re­ag­greg­ates the re­sponses back into a single mes­sage.

                                                                                                                                                                    图形/07inf32.gif



                                                                                                                                                                    Scatter -Gather将请求消息路由到多个接收者。然后,它使用聚合器来收集响应并将它们提炼成单个响应消息。

                                                                                                                                                                    The Scat­ter-Gather routes a re­quest mes­sage to a number of re­cip­i­ents. It then uses an Ag­greg­ator to col­lect the re­sponses and dis­till them into a single re­sponse mes­sage.

                                                                                                                                                                    Scatter-Gather有两种变体,它们使用不同的机制将请求消息发送到预期的接收者:

                                                                                                                                                                    There are two vari­ants of the Scat­ter-Gather that use dif­fer­ent mech­an­isms to send the re­quest mes­sages to the in­ten­ded re­cip­i­ents:

                                                                                                                                                                    1. 通过收件人列表进行分发允许Scatter -Gather控制收件人列表,但要求Scatter -Gather了解每个收件人的消息通道。

                                                                                                                                                                    2. Dis­tri­bu­tion via a Re­cip­i­ent List allows the Scat­ter-Gather to con­trol the list of re­cip­i­ents but re­quires the Scat­ter-Gather to be aware of each re­cip­i­ent's mes­sage chan­nel.

                                                                                                                                                                    3. 拍卖式分散-聚集使用发布-订阅通道将请求广播给任何感兴趣的参与者。此选项允许分散-聚集使用单个通道,但也会强制其放弃控制。

                                                                                                                                                                    4. Auc­tion-style Scat­ter-Gather uses a Pub­lish-Sub­scribe Chan­nel to broad­cast the re­quest to any in­ter­ested par­ti­cipant. This option allows the Scat­ter-Gather to use a single chan­nel but also forces it to re­lin­quish con­trol.

                                                                                                                                                                    该解决方案与组合消息处理器有相似之处。 我们不使用Splitter,而是使用Publish。我们很可能会添加一个返回地址,以便所有响应都可以通过单个通道进行处理。与组合消息处理器一样, Scatter -Gather根据定义的业务规则聚合响应。在我们的示例中,聚合器可能会从能够满足订单的供应商处获取最佳出价。使用Scatter-Gather聚合响应可能比使用Scatter-Gather 更困难组成消息处理器,因为我们可能不知道有多少接收者参与交互。

                                                                                                                                                                    The solu­tion shares sim­il­ar­it­ies with the Com­posed Mes­sage Pro­cessor. In­stead of using a Split­ter, we broad­cast the com­plete mes­sage to all in­volved parties using a Pub­lish-Sub­scribe Chan­nel. We will most likely add a Return Ad­dress so that all re­sponses can be pro­cessed through a single chan­nel. As with the Com­posed Mes­sage Pro­cessor, a Scat­ter-Gather ag­greg­ates re­sponses based on defined busi­ness rules. In our ex­ample, the Ag­greg­ator might take the best bids from sup­pli­ers that can fill the order. Ag­greg­at­ing the re­sponses can be more dif­fi­cult with a Scat­ter-Gather than with the Com­posed Mes­sage Pro­cessor, be­cause we may not know how many re­cip­i­ents par­ti­cip­ate in the in­ter­ac­tion.

                                                                                                                                                                    分散-收集组合消息处理器都将单个消息路由到多个收件人,并使用聚合器将各个回复消息组合回单个消息。组合消息处理器执行同步多个并行活动的任务。如果各个活动花费的时间差异很大,则即使许多子任务(甚至除了一项之外的所有子任务)都已完成,后续处理也会被拖延。这种考虑需要与Scatter-Gather带来的简单性和封装性进行权衡。两种选择之间的折衷方案可能是级联聚合器。这种设计允许在仅提供结果子集的情况下启动后续任务。

                                                                                                                                                                    Both the Scat­ter-Gather and the Com­posed Mes­sage Pro­cessor route a single mes­sage to mul­tiple re­cip­i­ents and com­bine the in­di­vidual reply mes­sages back into a single mes­sage by using an Ag­greg­ator. The Com­posed Mes­sage Pro­cessor per­forms the task of syn­chron­iz­ing mul­tiple par­al­lel activ­it­ies. If the in­di­vidual activ­it­ies take widely vary­ing amounts of time, sub­se­quent pro­cess­ing is held up even though many sub­tasks (or even all but one) have been com­pleted. This con­sid­er­a­tion needs to be weighed against the sim­pli­city and en­cap­su­la­tion the Scat­ter-Gather brings. A com­prom­ise between the two choices may be a cas­cad­ing Ag­greg­ator. This design allows sub­se­quent tasks to be ini­ti­ated with only a subset of the res­ults being avail­able.

                                                                                                                                                                    示例: 贷款经纪人

                                                                                                                                                                    Ex­ample: Loan Broker

                                                                                                                                                                    贷款经纪人示例(第 9 章,“插曲:组合消息传递”)使用Scatter-Gather将贷款报价请求路由到多个银行,并从传入响应中选择最佳报价。示例实现演示了基于接收者列表的解决方案(请参阅第 9 章中的“使用 MSMQ 的异步实现”)和发布-订阅通道(请参阅第9 章中的“使用 TIBCO ActiveEnterprise 的异步实现”)

                                                                                                                                                                    The Loan Broker ex­ample (Chapter 9, "In­ter­lude: Com­posed Mes­saging") uses a Scat­ter-Gather to route re­quests for a loan quote to a number of banks and select the best offer from the in­com­ing re­sponses. The ex­ample im­ple­ment­a­tions demon­strate both a solu­tion based on a Re­cip­i­ent List see "Asyn­chron­ous Im­ple­ment­a­tion with MSMQ" in Chapter 9and a Pub­lish-Sub­scribe Chan­nel see "Asyn­chron­ous Im­ple­ment­a­tion with TIBCO Act­iveEn­ter­prise" in Chapter 9.



                                                                                                                                                                    示例: 组合模式

                                                                                                                                                                    Ex­ample: Com­bin­ing Pat­terns

                                                                                                                                                                    我们现在可以使用Scatter-Gather来实现小部件和小工具订单处理示例。我们可以将分散-聚集组合消息处理器结合起来,处理每个传入订单,将其排序为单独的项目,将每个项目传递给投标,将每个项目的投标聚合为组合的投标响应,然后聚合所有投标响应成完整的报价。这是一个非常真实的例子,说明了如何将多个集成模式组合成一个完整的解决方案。将单个模式组合成更大的模式使我们能够在更高的抽象级别上讨论解决方案。它还允许我们修改实现的细节而不影响其他组件。

                                                                                                                                                                    We can now use the Scat­ter-Gather to im­ple­ment the widget and gadget order-pro­cess­ing ex­ample. We can com­bine the Scat­ter-Gather with the Com­posed Mes­sage Pro­cessor to pro­cess each in­com­ing order, se­quence it into in­di­vidual items, pass each item up for a bid, ag­greg­ate the bids for each item into a com­bined bid re­sponse, and then ag­greg­ate all bid re­sponses into a com­plete quote. This is a very real ex­ample of how mul­tiple in­teg­ra­tion pat­terns can be com­bined into a com­plete solu­tion. The com­pos­i­tion of in­di­vidual pat­terns into larger pat­terns allows us to dis­cuss the solu­tion at a higher level of ab­strac­tion. It also allows us to modify de­tails of the im­ple­ment­a­tion without af­fect­ing other com­pon­ents.

                                                                                                                                                                    组合分散-聚集和复合消息处理器

                                                                                                                                                                    Com­bin­ing a Scat­ter-Gather and a Com­pos­ite Mes­sage Pro­cessor

                                                                                                                                                                    图形/07inf33.gif

                                                                                                                                                                    这个例子还展示了聚合器​​的多功能性。 该解决方案使用两个聚合器来实现完全不同的目的。分散-聚集的第一个聚合器部分从许多供应商中选择最佳出价。该聚合器可能不需要所有供应商的响应(速度可能比低价更重要),但可能需要复杂的算法来组合响应。例如,订单可能包含 100 个小部件,而最低价格的供应商只有 60 个小部件库存。聚合商必须能够决定是否接受此报价并填写其他供应商的剩余 40 个项目。第二个聚合器组合消息处理器的一部分可能更简单,因为它只是连接从第一个聚合器收到的所有响应。然而,该聚合器需要确保所有响应实际上都已收到,并且需要处理错误情况,例如缺少项目响应。

                                                                                                                                                                    This ex­ample also shows the ver­sat­il­ity of the Ag­greg­ator. The solu­tion uses two Ag­greg­at­ors for quite dif­fer­ent pur­poses. The first Ag­greg­ator part of the Scat­ter-Gatherchooses the best bid from a number of vendors. This Ag­greg­ator may not re­quire a re­sponse from all vendors (speed may be more im­port­ant than a low price) but may re­quire a com­plex al­gorithm to com­bine re­sponses. For ex­ample, the order may con­tain 100 wid­gets, and the lowest price sup­plier has only 60 wid­gets in stock. The Ag­greg­ator must be able to decide whether to accept this offer and fill the re­main­ing 40 items from an­other sup­plier. The second Ag­greg­ator part of the Com­posed Mes­sage Pro­cessor might be sim­pler be­cause it simply con­cat­en­ates all re­sponses re­ceived from the first Ag­greg­ator. How­ever, this Ag­greg­ator needs to make sure that all re­sponses are in fact re­ceived and needs to deal with error con­di­tions such as miss­ing item re­sponses.



                                                                                                                                                                      路由表

                                                                                                                                                                      Routing Slip

                                                                                                                                                                      图形/routingslip_icon.gif

                                                                                                                                                                      到目前为止,大多数路由模式都会根据一组规则将传入消息路由到一个或多个目的地。但有时,我们不仅需要将消息路由到单个组件,还需要通过整个系列的组件。例如,假设我们使用管道和过滤器用于处理必须经过一系列处理步骤和业务规则验证的传入消息的体系结构。由于验证的性质差异很大,并且可能取决于外部系统(例如信用卡验证),因此我们将每种类型的步骤实现为单独的过滤器。每个过滤器都会检查传入的消息并将业务规则应用于该消息。如果消息不满足规则指定的条件,则将其路由到异常通道。过滤器之间的通道决定消息需要经历的验证顺序。

                                                                                                                                                                      Most of the rout­ing pat­terns presen­ted so far route in­com­ing mes­sages to one or more des­tin­a­tions based on a set of rules. Some­times, though, we need to route a mes­sage not just to a single com­pon­ent, but through a whole series of com­pon­ents. Let's assume, for ex­ample, that we use a Pipes and Fil­ters ar­chi­tec­ture to pro­cess in­com­ing mes­sages that have to un­dergo a se­quence of pro­cess­ing steps and busi­ness rule val­id­a­tions. Since the nature of the val­id­a­tions varies widely and may depend on ex­ternal sys­tems (e.g., credit card val­id­a­tions), we im­ple­ment each type of step as a sep­ar­ate filter. Each filter in­spects the in­com­ing mes­sage and ap­plies the busi­ness rule(s) to the mes­sage. If the mes­sage does not ful­fill the con­di­tions spe­cified by the rules, it is routed to an ex­cep­tion chan­nel. The chan­nels between the fil­ters de­term­ine the se­quence of val­id­a­tions that the mes­sage needs to un­dergo.

                                                                                                                                                                      不过,现在我们假设对每条消息执行的验证集取决于消息类型(例如,采购订单请求不需要信用卡验证,或者通过 VPN 发送订单的客户可能不需要解密和身份验证) 。为了满足这一要求,我们需要找到一种配置,可以根据消息的类型通过不同的过滤器序列路由消息。

                                                                                                                                                                      Now let's assume, though, that the set of val­id­a­tions to per­form against each mes­sage de­pends on the mes­sage type (for ex­ample, pur­chase order re­quests do not need credit card val­id­a­tion, or cus­tom­ers who send orders over a VPN may not re­quire de­cryp­tion and au­then­tic­a­tion). To ac­com­mod­ate this re­quire­ment, we need to find a con­fig­ur­a­tion that can route the mes­sage through a dif­fer­ent se­quence of fil­ters de­pend­ing on the type of the mes­sage.

                                                                                                                                                                      当设计时步骤顺序未知并且每条消息可能有所不同时,我们如何通过一系列处理步骤连续路由消息?

                                                                                                                                                                      How do we route a mes­sage con­sec­ut­ively through a series of pro­cess­ing steps when the se­quence of steps is not known at design time and may vary for each mes­sage?



                                                                                                                                                                      管道和过滤器架构风格为我们提供了一种优雅的方法,将一系列处理步骤表示为通过管道(通道)连接的独立过滤器。在默认配置中,过滤器通过固定管道连接。如果我们希望允许消息动态路由到不同的过滤器,我们可以使用充当消息路由器的特殊过滤器。路由器动态地确定将消息路由到的下一个过滤器。

                                                                                                                                                                      The Pipes and Fil­ters ar­chi­tec­tural style gives us an el­eg­ant ap­proach to rep­res­ent a se­quence of pro­cess­ing steps as in­de­pend­ent fil­ters con­nec­ted by pipes (chan­nels). In its de­fault con­fig­ur­a­tion, the fil­ters are con­nec­ted by fixed pipes. If we want to allow mes­sages to be routed to dif­fer­ent fil­ters dy­nam­ic­ally, we can use spe­cial fil­ters that act as Mes­sage Routers. The routers dy­nam­ic­ally de­term­ine the next filter to route the mes­sage to.

                                                                                                                                                                      好的解决方案的关键要求可以总结如下:

                                                                                                                                                                      The key re­quire­ments for a good solu­tion to our prob­lem can be sum­mar­ized as fol­lows:

                                                                                                                                                                      • 高效的消息流: 消息应仅流经所需的步骤,并避免不必要的组件。

                                                                                                                                                                      • Ef­fi­cient mes­sage flow: Mes­sages should flow through only the re­quired steps and avoid un­ne­ces­sary com­pon­ents.

                                                                                                                                                                      • 资源的高效利用: 解决方案不应使用大量的通道、路由器和其他资源。

                                                                                                                                                                      • Ef­fi­cient use of re­sources: The solu­tion should not use a huge amount of chan­nels, routers, and other re­sources.

                                                                                                                                                                      • 灵活: 各个消息所采用的路线应该易于更改。

                                                                                                                                                                      • Flex­ible: The route that in­di­vidual mes­sages take should be easy to change.

                                                                                                                                                                      • 易于维护: 如果需要支持新类型的消息,我们希望有单点维护以避免引入错误。

                                                                                                                                                                      • Simple to main­tain: If a new type of mes­sage needs to be sup­por­ted, we would like to have a single point of main­ten­ance to avoid in­tro­du­cing errors.

                                                                                                                                                                      下图说明了我们对该问题的替代解决方案。我们假设系统提供三个独立的处理步骤A、B和C,并且当前消息只需要经过步骤A和C。该示例消息的实际流程用粗箭头标记。

                                                                                                                                                                      The fol­low­ing dia­grams il­lus­trate our al­tern­at­ive solu­tions to the prob­lem. We assume that the system offers three sep­ar­ate pro­cess­ing steps, A, B, and C, and that the cur­rent mes­sage is re­quired to pass only through steps A and C. The actual flow for this ex­ample mes­sage is marked with thick arrows.

                                                                                                                                                                      图形/07inf34.gif

                                                                                                                                                                      我们可以形成一个由所有可能的验证步骤组成的长管道和过滤器链,并向每个路由器添加代码以绕过验证(如果所传递的消息类型不需要该步骤)(请参阅选项 A)。该选项本质上采用了消息过滤器中描述的反应式过滤方法。虽然该解决方案的简单性很有吸引力,但组件混合了业务逻辑(验证)和路由逻辑(决定是否验证)这一事实将使它们更难以重用。此外,可以想象两种类型的消息经历相似的处理步骤但顺序不同。这种硬连线方法不​​容易支持这一要求。

                                                                                                                                                                      We could form one long Pipes and Fil­ters chain of all pos­sible val­id­a­tion steps and add code to each router to bypass the val­id­a­tion if the step is not re­quired for the type of mes­sage being passed through (see Option A). This option es­sen­tially em­ploys the re­act­ive fil­ter­ing ap­proach de­scribed in the Mes­sage Filter. While the sim­pli­city of this solu­tion is ap­peal­ing, the fact that the com­pon­ents blend both busi­ness logic (the val­id­a­tion) and rout­ing logic (de­cid­ing whether to val­id­ate) will make them harder to reuse. Also, it is con­ceiv­able that two types of mes­sages un­dergo sim­ilar pro­cess­ing steps but in dif­fer­ent order. This hard-wired ap­proach would not easily sup­port this re­quire­ment.

                                                                                                                                                                      为了改善关注点分离并增加解决方案的可组合性,我们应该用基于内容的路由器替换每个组件内部的“门控”逻辑。然后,我们将到达所有可能的验证步骤链,每个步骤都以基于内容的路由器为前缀(请参阅选项 B)。当消息到达路由器时,它会检查消息的类型并确定该类型的消息是否需要验证一步在手。如果需要该步骤,路由器将通过验证路由消息。如果不需要该步骤,路由器将绕过验证并将消息直接路由到下一个路由器(以与 Detour 非常相似的方式545)。在每个步骤独立于任何其他步骤并且可以在每个步骤本地做出路由决策的情况下,此配置非常有效。不利的一面是,这种方法最终会通过一长串路由器来路由消息,即使只执行了几个验证步骤。事实上,每条消息将以两倍于可能组件数量的速率通过通道传输。如果我们有一个很大的组件库,这将导致一个相当简单的功能产生大量的消息流。此外,路由逻辑分布在许多过滤器中,因此很难理解特定类型的消息实际上将经历哪些验证步骤。同样,如果我们引入新的消息类型,我们可能必须更新每个路由器。最后,

                                                                                                                                                                      In order to im­prove sep­ar­a­tion of con­cerns and in­crease the com­pos­ab­il­ity of the solu­tion, we should re­place the "gating" logic inside each com­pon­ent with Con­tent-Based Routers. We would then arrive at a chain of all pos­sible val­id­a­tion steps, each pre­fixed by a Con­tent-Based Router see Option B. When a mes­sage ar­rives at the router, it checks the type of the mes­sage and de­term­ines whether this type of mes­sage re­quires the val­id­a­tion step at hand. If the step is re­quired, the router routes the mes­sage through the val­id­a­tion. If the step is not re­quired, the router by­passes the val­id­a­tion and routes the mes­sage dir­ectly to the next router (in a way very sim­ilar to the Detour 545). This con­fig­ur­a­tion works quite well in cases where each step is in­de­pend­ent from any other step and the rout­ing de­cision can be made loc­ally at each step. On the down­side, this ap­proach ends up rout­ing the mes­sage though a long series of routers even though only a few val­id­a­tion steps may be ex­ecuted. In fact, each mes­sage will be trans­mit­ted across a chan­nel at a rate of two times the number of pos­sible com­pon­ents. If we have a large com­pon­ent lib­rary, this will cause an enorm­ous amount of mes­sage flow for a rather simple func­tion. Also, the rout­ing logic is dis­trib­uted across many fil­ters, making it hard to un­der­stand which val­id­a­tion steps a mes­sage of a spe­cific type will ac­tu­ally un­dergo. Like­wise, if we in­tro­duce a new mes­sage type, we may have to update each and every router. Fi­nally, this option suf­fers from the same lim­it­a­tion as Option A in that mes­sages are tied to ex­ecut­ing steps in a pre­defined order.

                                                                                                                                                                      图形/07inf35.gif

                                                                                                                                                                      如果我们需要一个中心控制点,那么在我们之前的模式讨论中,预先的基于内容的路由器往往是一个不错的选择。我们可以设想一个解决方案,为每种消息类型设置单独的管道和过滤器链。每个链将包含与特定类型相关的验证序列。然后我们将使用基于内容的路由器根据消息类型将传入消息路由到正确的验证链(请参阅选项 C)。此方法仅通过相关步骤(加上初始路由器)路由消息。因此,这是迄今为止最有效的方法,因为我们仅添加一个路由步骤来实现所需的功能。该解决方案还强调了特定类型的消息将采取的路径非常好。然而,它要求我们硬连接任何可能的验证规则组合。此外,同一组件可以用在多个路径中。这种方法需要我们运行此类组件的多个实例,这会导致不必要的重复。对于大量消息类型,这种方法可能会导致维护噩梦,因为我们必须维护大量的组件实例和关联通道。综上所述,该解决方案非常高效,但牺牲了可维护性。

                                                                                                                                                                      If we desire a cent­ral point of con­trol, an up-front Con­tent-Based Router tended to be a good choice in our prior pat­tern dis­cus­sions. We could en­vi­sion a solu­tion where we set up in­di­vidual Pipes and Fil­ters chains for each mes­sage type. Each chain would con­tain the se­quence of val­id­a­tions rel­ev­ant to a spe­cific type. We would then use a Con­tent-Based Router to route the in­com­ing mes­sage to the cor­rect val­id­a­tion chain based on mes­sage type (see Option C). This ap­proach routes mes­sages only through the rel­ev­ant steps (plus the ini­tial router). Thus, it is the most ef­fi­cient ap­proach so far be­cause we add only a single rout­ing step in order to im­ple­ment the de­sired func­tion­al­ity. The solu­tion also high­lights the path that a mes­sage of a spe­cific type will take quite nicely. How­ever, it re­quires us to hard-wire any pos­sible com­bin­a­tion of val­id­a­tion rules. Also, the same com­pon­ent may be used in more than one path. This ap­proach would re­quire us to run mul­tiple in­stances of such com­pon­ents, which leads to un­ne­ces­sary du­plic­a­tion. For a large set of mes­sage types, this ap­proach could result in a main­ten­ance night­mare be­cause of the large number of com­pon­ent in­stances and as­so­ci­ated chan­nels that we have to main­tain. In sum­mary, this solu­tion is very ef­fi­cient at the ex­pense of main­tain­ab­il­ity.

                                                                                                                                                                      如果我们想避免硬连接验证步骤的所有可能组合,我们需要在每个验证步骤之间插入一个基于内容的路由器(请参阅选项 D)。为了避免遇到与反应式过滤方法(选项 B 中介绍)相关的相同问题,我们将在每个步骤之后而不是之前插入基于内容的路由器(我们需要在第一步之前添加一个额外的路由器来开始吧)。路由器足够聪明,可以将消息直接转发到下一个所需的验证步骤,而不是盲目地将其路由到下一个可用的验证步骤链中的一步。从抽象的角度来看,该解决方案看起来类似于反应式过滤方法,因为消息会遍历一组交替的路由器和过滤器。然而,在这种情况下,路由器比简单的是或否决策拥有更多智能,这使我们能够消除不必要的步骤。例如,在我们的简单场景中,消息仅通过两个路由器,而不是使用选项 B 的三个路由器。此选项提供了效率和灵活性,但并没有解决我们获得中央控制的目标,我们仍然必须维护潜在的大量路由器,因为路由逻辑分布在一系列独立的路由器上。

                                                                                                                                                                      If we want to avoid hard-wiring all pos­sible com­bin­a­tions of val­id­a­tion steps, we need to insert a Con­tent-Based Router between each val­id­a­tion step (see Option D). In order not to run into the same issues as­so­ci­ated with the re­act­ive fil­ter­ing ap­proach (presen­ted in Option B), we would insert the Con­tent-Based Router after each step in­stead of before (we need one ad­di­tional router in front of the very first step to get star­ted). The routers would be smart enough to relay the mes­sage dir­ectly to the next re­quired val­id­a­tion step in­stead of rout­ing it blindly to the next avail­able step in the chain. In the ab­stract, this solu­tion looks sim­ilar to the re­act­ive fil­ter­ing ap­proach be­cause the mes­sage tra­verses an al­tern­at­ing set of routers and fil­ters. How­ever, in this case the routers pos­sess more in­tel­li­gence than a simple yes or no de­cision, which allows us to elim­in­ate un­ne­ces­sary steps. For ex­ample, in our simple scen­ario, the mes­sage passes only through two routers as op­posed to three with Option B. This option provides ef­fi­ciency and flex­ib­il­ity but does not solve our goal of ob­tain­ing cent­ral con­trolwe still have to main­tain a po­ten­tially large number of routers be­cause the rout­ing logic is spread out across a series of in­de­pend­ent routers.

                                                                                                                                                                      为了解决最后一个缺点,我们可以将所有路由器组合成一个“超级路由器”(参见选项 E)。在每个验证步骤之后,消息将被路由回超级路由器,超级路由器将确定要执行的下一个验证步骤。此配置仅将消息路由到特定消息类型所需的过滤器。由于所有路由决策现在都合并到单个路由器中,因此我们需要设计一种机制来记住我们已经完成处理的步骤。因此,超级路由器必须是有状态的,或者每个过滤器必须在消息上附加一个标签,告诉超级路由器该消息经过的最后一个过滤器的名称。还,我们仍在处理这样一个事实:每个验证步骤都需要通过两个通道传递消息:传递到组件,然后返回到超级路由器。这导致的流量大约是选项 C 的两倍。

                                                                                                                                                                      To ad­dress this last short­com­ing, we could com­bine all routers into a single "super-router" (see Option E). After each val­id­a­tion step, the mes­sage would be routed back to the super-router, which would de­term­ine the next val­id­a­tion step to be ex­ecuted. This con­fig­ur­a­tion routes the mes­sage only to those fil­ters that are re­quired for the spe­cific type of mes­sage. Since all the rout­ing de­cisions are now in­cor­por­ated into a single router, we need to devise a mech­an­ism to re­mem­ber which steps we already fin­ished pro­cess­ing. There­fore, the super-router would have to be state­ful or each filter would have to attach a tag to the mes­sage telling the super-router the name of the last filter the mes­sage went through. Also, we are still deal­ing with the fact that each val­id­a­tion step re­quires the mes­sage to be passed through two chan­nels: to the com­pon­ent and then back to the super-router. This res­ults in about two times as much traffic as Option C.

                                                                                                                                                                      将路由单附加到每条消息,指定处理步骤的顺序。用一个特殊的消息路由器包装每个组件,该路由器读取路由表并将消息路由到列表中的下一个组件。

                                                                                                                                                                      Attach a Rout­ing Slip to each mes­sage, spe­cify­ing the se­quence of pro­cess­ing steps. Wrap each com­pon­ent with a spe­cial mes­sage router that reads the Rout­ing Slip and routes the mes­sage to the next com­pon­ent in the list.

                                                                                                                                                                      图形/07inf36.gif



                                                                                                                                                                      我们在流程的开头插入一个特殊组件,用于计算每条消息所需步骤的列表。然后,它将列表作为路由表附加到消息,并通过将消息路由到第一个处理步骤来启动该流程。成功处理后,每个处理步骤都会查看路由表并将消息传递到路由表中指定的下一个处理步骤。

                                                                                                                                                                      We insert a spe­cial com­pon­ent into the be­gin­ning of the pro­cess that com­putes the list of re­quired steps for each mes­sage. It then at­taches the list as a Rout­ing Slip to the mes­sage and starts the pro­cess by rout­ing the mes­sage to the first pro­cess­ing step. After suc­cess­ful pro­cess­ing, each pro­cess­ing step looks at the Rout­ing Slip and passes the mes­sage to the next pro­cess­ing step spe­cified in the rout­ing table.

                                                                                                                                                                      此模式的工作原理类似于附在杂志上以便在小组或部门中流通的传送单。唯一的区别是,路由表有一个明确的遍历组件顺序,而在大多数公司中,你可以在阅读完杂志后将其交给列表中任何未读过的人(当然,老板通常是第一位的) 。

                                                                                                                                                                      This pat­tern works sim­il­arly to the rout­ing slip at­tached to a magazine for cir­cu­la­tion in a group or de­part­ment. The only dif­fer­ence is that the Rout­ing Slip has a defined se­quence of com­pon­ents it tra­verses, whereas in most com­pan­ies you can hand the magazine after read­ing it to any person on the list who has not read it (of course, the boss usu­ally comes first).

                                                                                                                                                                      Routing Slip将超级路由器方法(选项 E)的中央控制与硬连线解决方案(选项 C)的效率相结合。我们预先确定完整的路由方案并将其附加到消息中,因此我们不必返回中央路由器进行进一步决策。每个组件都通过简单的路由逻辑进行了增强。在所提出的解决方案中,我们假设该路由逻辑内置于处理组件本身中。如果我们回顾一下选项 A,我们会记得我们之所以放弃这种方法,部分原因是我们必须将一些逻辑硬编码到每个组件中。路由表如何更好?主要区别在于路由表中使用的路由器是通用的,不必随着路由逻辑的变化而改变。合并到每个组件中的路由逻辑类似于返回地址,其中返回地址是从地址列表中选择的。与Return Address 类似,即使组件中内置了一段路由逻辑,组件仍保留其可重用性和可组合性。此外,路由表的计算现在可以在中心位置完成,而无需触及任何处理组件内的代码。

                                                                                                                                                                      The Rout­ing Slip com­bines the cent­ral con­trol of the super-router ap­proach (Option E) with the ef­fi­ciency of the hard-wired solu­tion (Option C). We de­term­ine the com­plete rout­ing scheme up front and attach it to the mes­sage, so we do not have to return to the cent­ral router for fur­ther de­cision making. Each com­pon­ent is aug­men­ted by simple rout­ing logic. In the pro­posed solu­tion, we assume that this rout­ing logic is built into the pro­cess­ing com­pon­ent itself. If we look back at Option A, we re­mem­ber that we dis­missed this ap­proach partly be­cause we had to hard-code some logic into each com­pon­ent. How is the Rout­ing Slip better? The key dif­fer­ence is that the router used in the Rout­ing Slip is gen­eric and does not have to change with changes in the rout­ing logic. The rout­ing logic in­cor­por­ated into each com­pon­ent is sim­ilar to a Return Ad­dress, where the return ad­dress is se­lec­ted from a list of ad­dresses. Sim­il­arly to the Return Ad­dress, the com­pon­ents retain their re­usab­il­ity and com­pos­ab­il­ity even though a piece of rout­ing logic is built into the com­pon­ent. Ad­di­tion­ally, the com­pu­ta­tion of the rout­ing table can now be done in a cent­ral place without ever touch­ing the code inside any of the pro­cess­ing com­pon­ents.

                                                                                                                                                                      一如既往,天下没有免费的午餐,因此我们可以预期路由表会有一些限制。首先,消息大小略有增加。在大多数情况下,这应该是微不足道的,但我们需要意识到我们现在在消息中携带进程状态(哪些步骤已完成)。这可能会导致其他副作用。例如,如果我们丢失一条消息,我们不仅会丢失消息数据,还会丢失流程数据(即消息接下来要去的地方)。在许多情况下,将所有消息的状态维护在一个中心位置以执行报告或错误恢复可能很有用。

                                                                                                                                                                      As always, there is no free lunch, so we can expect the Rout­ing Slip to have some lim­it­a­tions. First, the mes­sage size in­creases slightly. In most cases, this should be in­sig­ni­fic­ant, but we need to real­ize that we are now car­ry­ing pro­cess state (which steps have been com­pleted) inside the mes­sage. This can cause other side ef­fects. For ex­ample, if we lose a mes­sage, we lose not only the mes­sage data but also the pro­cess data (i.e., where the mes­sage was going to go next). In many cases, it may be useful to main­tain the state of all mes­sages in a cent­ral place to per­form re­port­ing or error re­cov­ery.

                                                                                                                                                                      路由表的另一个限制是消息的路径一旦开始就不能更改。这意味着消息路径不能依赖于沿途处理步骤生成的中间结果。不过,在许多现实业务流程中,消息流确实会根据中间结果而发生变化。例如,根据订购商品的可用性(如库存系统报告),我们可能希望继续使用不同的路径。这也意味着中央实体必须能够提前确定消息应经历的所有步骤。这可能会导致设计中出现一些脆弱性,类似于使用基于内容的路由器的担忧。

                                                                                                                                                                      An­other lim­it­a­tion of the Rout­ing Slip is that the path of a mes­sage cannot be changed once it is un­der­way. This im­plies that the mes­sage path cannot depend on in­ter­me­di­ate res­ults gen­er­ated by a pro­cess­ing step along the way. In many real-life busi­ness pro­cesses the mes­sage flow does change based on in­ter­me­di­ate res­ults, though. For ex­ample, de­pend­ing on the avail­ab­il­ity of the ordered items (as re­por­ted by the in­vent­ory system), we may want to con­tinue with a dif­fer­ent path. This also means that a cent­ral entity has to be able to de­term­ine all steps a mes­sage should un­dergo in ad­vance. This can lead to some brit­tle­ness in the design, sim­ilar to the con­cerns about using a Con­tent-Based Router.

                                                                                                                                                                      使用旧应用程序实施路由表

                                                                                                                                                                      Im­ple­ment­ing a Rout­ing Slip with Legacy Ap­plic­a­tions

                                                                                                                                                                      路由假设我们有能力使用路由器逻辑来增强各个组件。如果我们处理遗留应用程序或打包应用程序,我们可能无法影响组件本身的功能。相反,我们需要使用外部路由器通过消息传递与组件进行通信。这不可避免地增加了使用的通道和组件的数量。然而,路由表仍然提供了我们的效率、灵活性和可维护性目标之间的最佳权衡。

                                                                                                                                                                      The Rout­ing Slip as­sumes that we have the abil­ity to aug­ment the in­di­vidual com­pon­ents with the router logic. If we are deal­ing with legacy ap­plic­a­tions or pack­aged ap­plic­a­tions, we may not be able to in­flu­ence the func­tion­al­ity of the com­pon­ent itself. Rather, we need to use an ex­ternal router that com­mu­nic­ates with the com­pon­ent via mes­saging. This in­ev­it­ably in­creases the number of chan­nels and com­pon­ents in use. How­ever, the Rout­ing Slip still provides the best trade-off between our goals of ef­fi­ciency, flex­ib­il­ity, and main­tain­ab­il­ity.

                                                                                                                                                                      使用旧应用程序实施路由表

                                                                                                                                                                      Im­ple­ment­ing a Rout­ing Slip with Legacy Ap­plic­a­tions

                                                                                                                                                                      图形/07inf37.gif

                                                                                                                                                                      常见用法

                                                                                                                                                                      Common Usage

                                                                                                                                                                      路由在以下场景中最有用:

                                                                                                                                                                      The Rout­ing Slip is most useful in the fol­low­ing scen­arios:

                                                                                                                                                                      1. 一系列二进制验证步骤。通过不在消息中添加信息,一旦消息正在进行就无法更改路由的限制不再是一个因素。我们仍然赞赏通过重新配置中央路由表来更改验证步骤顺序的灵活性。每个组件都可以选择因错误而中止序列或将消息传递到下一步。

                                                                                                                                                                      2. A se­quence of binary val­id­a­tion steps. By not adding in­form­a­tion to the mes­sage, the lim­it­a­tion that we cannot change the rout­ing once the mes­sage is un­der­way is no longer a factor. We still ap­pre­ci­ate the flex­ib­il­ity to change the se­quence of val­id­a­tion steps by re­con­fig­ur­ing the cent­ral Rout­ing Slip. Each com­pon­ent has the choice between abort­ing the se­quence due to error or passing the mes­sage on to the next step.

                                                                                                                                                                      3. 每一步都是无状态转换。例如,假设我们收到来自多个业务合作伙伴的订单。所有订单均通过公共渠道到达,但根据合作伙伴的不同采用不同的格式。因此,每条消息可能需要不同的转换步骤。部分合作伙伴的消息可能需要解密;其他人可能不会。有些可能需要转型或丰富;其他人可能不会。为每个合作伙伴保留一个路由表让我们可以轻松地在中央位置为每个合作伙伴重新配置步骤。

                                                                                                                                                                      4. Each step is a state­less trans­form­a­tion. For ex­ample, let's assume that we re­ceive orders from a vari­ety of busi­ness part­ners. All orders arrive on a common chan­nel but in dif­fer­ent formats de­pend­ing on the part­ner. As a result, each mes­sage may re­quire dif­fer­ent trans­form­a­tion steps. Mes­sages from some part­ners may re­quire de­cryp­tion; others may not. Some may re­quire trans­form­a­tion or en­rich­ment; others may not. Keep­ing a Rout­ing Slip for each part­ner gives us an easy way to re­con­fig­ure the steps for each part­ner in a cent­ral loc­a­tion.

                                                                                                                                                                      5. 每个步骤都会收集数据,但不做出任何决定(请参阅内容丰富器)。在某些情况下,我们会收到一条包含其他数据的引用标识符的消息。例如,如果我们收到 DSL 线路的订单,则该消息可能仅包含申请人的家庭电话号码。我们需要通过外部来源来确定客户的名称、为线路提供服务的中心办公室、距中心办公室的距离等。一旦我们获得包含所有相关数据的完整消息,我们就可以决定向客户提供什么套餐。在这种情况下,决策会被推迟到最后,因此我们可以使用路由表来收集必要的信息。不过,我们需要评估我们是否真的需要灵活性路由表否则,管道和过滤器的简单硬连线链可能就足够了。

                                                                                                                                                                      6. Each step gath­ers data, but makes no de­cisions (see Con­tent En­richer ). In some cases, we re­ceive a mes­sage that con­tains ref­er­ence iden­ti­fi­ers to other data. For ex­ample, if we re­ceive an order for a DSL line, the mes­sage may con­tain only the home phone number of the ap­plic­ant. We need to go to ex­ternal sources to de­term­ine the cus­tomer's name, the cent­ral office ser­vi­cing the line, the dis­tance from the cent­ral office, and so on. Once we have a com­plete mes­sage with all rel­ev­ant data, we can decide what pack­age to offer to the cus­tomer. In this scen­ario, the de­cision is post­poned until the end, so we can use a Rout­ing Slip to col­lect the ne­ces­sary in­form­a­tion. We need to assess, though, whether we really re­quire the flex­ib­il­ity of the Rout­ing Slip. Oth­er­wise, a simple hard-wired chain of Pipes and Fil­ters may be suf­fi­cient.

                                                                                                                                                                      使用路由表实现简单路由器

                                                                                                                                                                      Im­ple­ment­ing a Simple Router with a Rout­ing Slip

                                                                                                                                                                      基于内容的路由器的缺点之一是它必须合并有关每个可能的接收者以及与该接收者关联的路由规则的知识。在松散耦合的精神下,可能不希望有一个包含许多其他组件知识的中心组件。基于内容的路由器的替代解决方案是发布-订阅通道与消息过滤器数组相结合如消息过滤器中所述。该解决方案允许每个收件人决定处理哪些消息,但存在重复消息处理的风险。使各个收件人能够决定是否处理给定消息的另一个选项是使用路由单的修改版本作为责任链,如 [GoF] 中所述。 责任链允许每个组件接受消息或将其路由到列表中的下一个组件。路由是所有参与者的静态列表。这仍然意味着中心组件必须了解所有可能的接收者。然而,组件不需要知道每个组件消耗了哪些消息。

                                                                                                                                                                      One of the down­sides of a Con­tent-Based Router is that it has to in­cor­por­ate know­ledge about each pos­sible re­cip­i­ent and the rout­ing rules as­so­ci­ated with that re­cip­i­ent. Under the spirit of loose coup­ling, it may be un­desir­able to have a cent­ral com­pon­ent that in­cor­por­ates know­ledge about many other com­pon­ents. An al­tern­at­ive solu­tion to the Con­tent-Based Router is a Pub­lish-Sub­scribe Chan­nel com­bined with an array of Mes­sage Fil­ters as de­scribed in the Mes­sage Filter. This solu­tion allows each re­cip­i­ent to decide which mes­sages to pro­cess but suf­fers from risk of du­plic­ate mes­sage pro­cess­ing. An­other option to enable in­di­vidual re­cip­i­ents to decide whether to pro­cess a given mes­sage is to use a mod­i­fied ver­sion of a Rout­ing Slip acting as a Chain of Re­spons­ib­il­ity as de­scribed in [GoF]. The Chain of Re­spons­ib­il­ity allows each com­pon­ent to accept a mes­sage or route it to the next com­pon­ent in the list. The Rout­ing Slip is a static list of all par­ti­cipants. This still im­plies that a cent­ral com­pon­ent has to have know­ledge of all pos­sible re­cip­i­ents. How­ever, the com­pon­ent does not need to know which mes­sages each com­pon­ent con­sumes.

                                                                                                                                                                      图形/07inf38.gif

                                                                                                                                                                      使用路由表可以避免重复消息处理的风险。同样,很容易确定消息是否未被任何组件处理。主要的权衡是处理速度较慢和网络流量增加。基于内容的路由器无论系统数量如何都会发布单个消息,而路由表方法发布的平均消息数量等于系统数量的一半。如果我们能够以这样的方式安排系统,使得第一个接收消息的系统有更高的机会处理该消息,那么我们可以减少这个数字,但消息的数量可能仍然高于基于内容的预测路由器

                                                                                                                                                                      Using a Rout­ing Slip avoids the risk of du­plic­ate mes­sage pro­cess­ing. Like­wise, it is easy to de­term­ine if a mes­sage was not pro­cessed by any com­pon­ent. The main trade-off is the slower pro­cess­ing and in­creased net­work traffic. While a Con­tent-Based Router pub­lishes a single mes­sage re­gard­less of the number of sys­tems, the Rout­ing Slip ap­proach pub­lishes an av­er­age number of mes­sages equal to half the number of sys­tems. We can reduce this number if we can ar­range the sys­tems in such a way that the first sys­tems to re­ceive the mes­sage have a higher chance of hand­ling the mes­sage, but the number of mes­sages will likely remain higher than with a pre­dict­ive Con­tent-Based Router.

                                                                                                                                                                      在某些情况下,我们需要比简单的顺序列表更多的控制,或者我们需要根据中间结果更改消息流。Process Manager可以满足这些要求,因为它支持分支条件、分叉和连接。实质上,路由表是动态配置的业务流程的特例。应仔细检查使用路由表和使用中央流程管理器之间的权衡。动态路由表结合了中心维护点的优势和硬连线解决方案的效率。然而,随着复杂性的增加,分析和调试系统可能变得越来越困难,因为路由状态信息分布在消息中。此外,随着流程定义的语义开始包含决策、分叉和连接等结构,配置文件可能会变得难以理解和维护。我们可以在路由表中包含条件语句,并扩充每个组件中的路由模块来解释条件命令以确定下一个路由位置。不过,我们需要小心,不要因为额外的功能而使该解决方案的简单性负担过重。Routing Slip并开始使用功能更强大的流程管理器

                                                                                                                                                                      There are cases where we need more con­trol than a simple se­quen­tial list or we need to change the flow of a mes­sage based on in­ter­me­di­ate res­ults. The Pro­cess Man­ager can ful­fill these re­quire­ments be­cause it sup­ports branch­ing con­di­tions, forks, and joins. In es­sence, the Rout­ing Slip is a spe­cial case of a dy­nam­ic­ally con­figured busi­ness pro­cess. The trade-offs between using a Rout­ing Slip and using a cent­ral Pro­cess Man­ager should be care­fully ex­amined. A dy­namic Rout­ing Slip com­bines the be­ne­fits of a cent­ral point of main­ten­ance with the ef­fi­ciency of a hard-wired solu­tion. How­ever, as the com­plex­ity grows, ana­lyz­ing and de­bug­ging the system may become in­creas­ingly dif­fi­cult, since the rout­ing state in­form­a­tion is dis­trib­uted across the mes­sages. Also, as the se­mantics of the pro­cess defin­i­tion begin to in­clude con­structs such as de­cisions, forks, and joins, the con­fig­ur­a­tion file may become hard to un­der­stand and main­tain. We could in­clude con­di­tional state­ments inside the rout­ing table and aug­ment the rout­ing mod­ules in each com­pon­ent to in­ter­pret the con­di­tional com­mands to de­term­ine the next rout­ing loc­a­tion. We need to be care­ful, though, to not over­bur­den the sim­pli­city of this solu­tion with ad­di­tional func­tion­al­ity. Once we re­quire this type of com­plex­ity, it may be a good idea to give up the runtime ef­fi­ciency of a Rout­ing Slip and to start using a much more power­ful Pro­cess Man­ager.

                                                                                                                                                                      示例: 作为组合服务的路由表

                                                                                                                                                                      Ex­ample: Rout­ing Slip as a Com­posed Ser­vice

                                                                                                                                                                      创建面向服务的体系结构时,单个逻辑功能通常由多个独立步骤组成。这种情况的发生通常有两个主要原因。首先,打包应用程序倾向于公开基于其内部 API 的细粒度接口。当将这些包集成到集成解决方案中时,我们希望在更高的抽象级别上工作。例如,“新帐户”操作可能需要计费系统内的多个步骤:创建新客户、选择服务计划、设置地址属性、验证信用数据等。其次,单个逻辑功能可能分布在多个系统中。我们希望向其他系统隐藏这一事实,以便我们可以灵活地在系统之间重新分配职责,而不会影响集成解决方案的其余部分。我们可以轻松地使用路由滑动执行多个内部步骤以响应单个请求消息。路由使我们能够灵活地从同一通道执行不同的请求。路由执行各个步骤的序列,但在外部看来就像单个步骤(参见下页的图)。

                                                                                                                                                                      When cre­at­ing a ser­vice-ori­ented ar­chi­tec­ture, a single lo­gical func­tion is often com­posed of mul­tiple in­de­pend­ent steps. This situ­ation occurs com­monly for two primary reas­ons. First, pack­aged ap­plic­a­tions tend to expose fine-grained in­ter­faces based on their in­ternal APIs. When in­teg­rat­ing these pack­ages into an in­teg­ra­tion solu­tion, we want to work at a higher level of ab­strac­tion. For ex­ample, the op­er­a­tion "New Ac­count" may re­quire mul­tiple steps inside a billing system: Create a new cus­tomer, select a ser­vice plan, set ad­dress prop­er­ties, verify credit data, and so on. Second, a single lo­gical func­tion may be spread across more than one system. We want to hide this fact from other sys­tems so we have the flex­ib­il­ity to re­as­sign re­spons­ib­il­it­ies between sys­tems without af­fect­ing the rest of the in­teg­ra­tion solu­tion. We can easily use a Rout­ing Slip to ex­ecute mul­tiple in­ternal steps in re­sponse to a single re­quest mes­sage. The Rout­ing Slip gives us the flex­ib­il­ity to ex­ecute dif­fer­ent re­quests from the same chan­nel. The Rout­ing Slip ex­ecutes the se­quence of in­di­vidual steps but ap­pears to the out­side like a single step (see figure on the fol­low­ing page).

                                                                                                                                                                      使用路由表作为组合服务

                                                                                                                                                                      Using a Rout­ing Slip as a Com­posed Ser­vice

                                                                                                                                                                      图形/07inf39.gif

                                                                                                                                                                      1. 指定预期操作和任何必要数据的传入请求消息被发送到查找组件。

                                                                                                                                                                      2. The in­com­ing re­quest mes­sage, spe­cify­ing the in­ten­ded op­er­a­tion and any ne­ces­sary data, is sent to the lookup com­pon­ent.

                                                                                                                                                                      3. 查找组件从服务目录中检索与预期操作相关联的所需处理步骤的列表。它将通道列表(每个通道相当于一个细粒度操作)添加到消息头中。查找组件将返回通道添加到列表中,以便将完成的消息返回到查找组件。

                                                                                                                                                                      4. The lookup com­pon­ent re­trieves the list of re­quired pro­cess­ing steps as­so­ci­ated with the in­ten­ded op­er­a­tion from a ser­vice dir­ect­ory. It adds the list of chan­nels (each chan­nel equals one fine-grained op­er­a­tion) to the mes­sage header. The lookup com­pon­ent adds the return chan­nel to the list so that com­pleted mes­sages are re­turned to the lookup com­pon­ent.

                                                                                                                                                                      5. 查找组件将消息发布到第一个活动的通道。

                                                                                                                                                                      6. The lookup com­pon­ent pub­lishes the mes­sage to the chan­nel for the first activ­ity.

                                                                                                                                                                      7. 每个路由器从队列中读取请求并将其传递给服务提供者。执行后,路由器将活动标记为已完成,并将消息路由到路由表中指定的下一个通道。

                                                                                                                                                                      8. Each router reads the re­quest from the queue and passes it to the ser­vice pro­vider. After the ex­e­cu­tion, the router marks the activ­ity as com­pleted and routes the mes­sage to the next chan­nel spe­cified in the rout­ing table.

                                                                                                                                                                      9. 查找组件使用返回通道中的消息并将其转发给请求者。在外界看来,这整个过程似乎是一个简单的请求-回复消息交换。

                                                                                                                                                                      10. The lookup com­pon­ent con­sumes the mes­sage off the return chan­nel and for­wards it to the re­questor. To the out­side, this whole pro­cess ap­pears to be a simple re­quest-reply mes­sage ex­change.



                                                                                                                                                                      示例: WS 路由

                                                                                                                                                                      Ex­ample: WS-Rout­ing

                                                                                                                                                                      通常,Web 服务请求必须通过多个中介进行路由。为此,Microsoft 定义了 Web 服务路由协议 (WS-Routing) 规范。WS-Routing 是一种基于 SOAP 的协议,用于将消息从发送方通过一系列中介路由到接收方。WS-Routing 的语义比Routing Slip更丰富,但是Routing Slip可以在 WS-Routing 中轻松实现。以下示例显示了通过中介 B 和 C(由元素<wsrp:via>表示)从节点 A 路由到节点 D 的消息的 SOAP 标头。

                                                                                                                                                                      Fre­quently, a Web ser­vice re­quest has to be routed through mul­tiple in­ter­me­di­ar­ies. For this pur­pose, Mi­crosoft defined the Web Ser­vices Rout­ing Pro­tocol (WS-Rout­ing) spe­cific­a­tion. WS-Rout­ing is a SOAP-based pro­tocol for rout­ing mes­sages from a sender through a series of in­ter­me­di­ar­ies to a re­ceiver. The se­mantics of WS-Rout­ing are richer than those of the Rout­ing Slip, but a Rout­ing Slip can be easily im­ple­men­ted in WS-Rout­ing. The fol­low­ing ex­ample shows the SOAP header for a mes­sage that is routed from node A to node D via the in­ter­me­di­ar­ies B and C (de­noted by the ele­ment <wsrp:via>).

                                                                                                                                                                      <SOAP-ENV:信封
                                                                                                                                                                            xmlns:SOAP-ENV="http://www.w3.org/2001/06/soap-envelope">
                                                                                                                                                                         <SOAP-ENV:标头>
                                                                                                                                                                            <wsrp:路径 xmlns:wsrp="http://schemas.xmlsoap.org/rp/">
                                                                                                                                                                               <wsrp:action>http://www.im.org/chat</wsrp:action>
                                                                                                                                                                               <wsrp:to>soap://D.com/some/endpoint</wsrp:to>
                                                                                                                                                                               <wsrp:转发>
                                                                                                                                                                                  <wsrp:via>soap://B.com</wsrp:via>
                                                                                                                                                                                  <wsrp:via>soap://C.com</wsrp:via>
                                                                                                                                                                               </wsrp:转发>
                                                                                                                                                                               <wsrp:from>soap://A.com/some/endpoint</wsrp:from>
                                                                                                                                                                               <wsrp:id>uuid:84b9f5d0-33fb-4a81-b02b-5b760641c1d6</wsrp:id>
                                                                                                                                                                            </wsrp:路径>
                                                                                                                                                                         </SOAP-ENV:标头>
                                                                                                                                                                         <SOAP-ENV:正文>
                                                                                                                                                                            ...
                                                                                                                                                                         </SOAP-ENV:正文>
                                                                                                                                                                      </SOAP-ENV:信封>
                                                                                                                                                                      
                                                                                                                                                                      <SOAP-ENV:En­vel­ope
                                                                                                                                                                            xmlns:SOAP-ENV="http://www.w3.org/2001/06/soap-en­vel­ope">
                                                                                                                                                                         <SOAP-ENV:Header>
                                                                                                                                                                            <wsrp:path xmlns:wsrp="http://schemas.xmlsoap.org/rp/">
                                                                                                                                                                               <wsrp:action>http://www.im.org/chat</wsrp:action>
                                                                                                                                                                               <wsrp:to>soap://D.com/some/en­d­point</wsrp:to>
                                                                                                                                                                               <wsrp:fwd>
                                                                                                                                                                                  <wsrp:via>soap://B.com</wsrp:via>
                                                                                                                                                                                  <wsrp:via>soap://C.com</wsrp:via>
                                                                                                                                                                               </wsrp:fwd>
                                                                                                                                                                               <wsrp:from>soap://A.com/some/en­d­point</wsrp:from>
                                                                                                                                                                               <wsrp:id>uuid:84b9f5d0-33fb-4a81-b02b-5b760641c1d6</wsrp:id>
                                                                                                                                                                            </wsrp:path>
                                                                                                                                                                         </SOAP-ENV:Header>
                                                                                                                                                                         <SOAP-ENV:Body>
                                                                                                                                                                            ...
                                                                                                                                                                         </SOAP-ENV:Body>
                                                                                                                                                                      </SOAP-ENV:En­vel­ope>
                                                                                                                                                                      

                                                                                                                                                                      与大多数 Web 服务规范一样,WS-Routing 可能会随着时间的推移而发展和/或与其他规范合并。我们在此提供了这个示例,作为 Web 服务社区在路由方面的发展方向的快照。

                                                                                                                                                                      Like most Web ser­vices spe­cific­a­tions, WS-Rout­ing is likely to evolve over time and/or be merged with other spe­cific­a­tions. We in­cluded the ex­ample here as a snap­shot of where the Web ser­vices com­munity is going with re­spect to rout­ing.



                                                                                                                                                                        流程经理

                                                                                                                                                                        Process Manager

                                                                                                                                                                        图形/processmanager_icon.gif

                                                                                                                                                                        路由演示了如何通过一系列动态处理步骤来路由消息。路由表的解决方案基于两个关键假设:必须预先确定处理步骤的顺序,并且该顺序是线性的。在许多情况下,这些假设可能无法实现。例如,可能必须根据中间结果做出路由决策。或者,处理步骤可能不是连续的,而是多个步骤可以并行执行。

                                                                                                                                                                        The Rout­ing Slip demon­strates how a mes­sage can be routed through a dy­namic series of pro­cess­ing steps. The solu­tion of the Rout­ing Slip is based on two key as­sump­tions: The se­quence of pro­cess­ing steps has to be de­term­ined up front, and the se­quence is linear. In many cases, these as­sump­tions may not be ful­filled. For ex­ample, rout­ing de­cisions might have to be made based on in­ter­me­di­ate res­ults. Or, the pro­cess­ing steps may not be se­quen­tial, but mul­tiple steps might be ex­ecuted in par­al­lel.

                                                                                                                                                                        当所需的步骤在设计时可能未知并且可能不连续时,我们如何通过多个处理步骤路由消息?

                                                                                                                                                                        How do we route a mes­sage through mul­tiple pro­cess­ing steps when the re­quired steps may not be known at design time and may not be se­quen­tial?



                                                                                                                                                                        管道和过滤器架构风格的主要优点之一是通过将各个处理单元(“过滤器”)与通道(“管道”)连接来将它们组合成一个序列。然后,每条消息都通过一系列处理单元(或组件)进行路由。如果我们需要能够更改每条消息的顺序,我们可以使用多个基于内容的路由器。该解决方案提供了最大的灵活性,但缺点是路由逻辑分布在许多路由组件中。路由通过预先计算消息路径提供中心控制点,但不提供根据中间结果重新路由消息或同时执行多个步骤的灵活性。

                                                                                                                                                                        One of the primary ad­vant­ages of a Pipes and Fil­ters ar­chi­tec­tural style is the com­pos­ab­il­ity of in­di­vidual pro­cess­ing units ("fil­ters") into a se­quence by con­nect­ing them with chan­nels ("pipes"). Each mes­sage is then routed through the se­quence of pro­cess­ing units (or com­pon­ents). If we need to be able to change the se­quence for each mes­sage, we can use mul­tiple Con­tent-Based Routers. This solu­tion provides the max­imum flex­ib­il­ity but has the dis­ad­vant­age that the rout­ing logic is spread across many rout­ing com­pon­ents. The Rout­ing Slip provides a cent­ral point of con­trol by com­put­ing the mes­sage path up front, but does not provide the flex­ib­il­ity to reroute the mes­sage based on in­ter­me­di­ate res­ults or to ex­ecute mul­tiple steps sim­ul­tan­eously.

                                                                                                                                                                        如果在每个单独的处理单元之后,我们将控制权返回给中央组件,我们就可以获得灵活性并保持中央控制点。然后该组件可以确定下一个要执行的处理单元。按照这种方法,我们最终得到一个交替的处理流程:中央组件、处理单元、中央组件、处理单元等等。结果,中央单元在每个单独的处理步骤之后接收消息。当消息到达时,中央组件必须根据中间结果和序列中的当前步骤确定要执行的下一个处理步骤。这将需要各个处理单元向中央单元返回足够的信息来做出该决定。然而,这种方法将使处理单元依赖于中央单元的存在,因为它们可能必须传递与处理单元无关而仅与中央组件相关的无关信息。如果我们想要将各个处理步骤和关联的消息格式与中央单元解耦,我们需要为中央单元提供某种形式的“内存”,告诉它序列中最后执行的步骤。

                                                                                                                                                                        We can gain flex­ib­il­ity and main­tain a cent­ral point of con­trol if, after each in­di­vidual pro­cess­ing unit, we return con­trol back to a cent­ral com­pon­ent. That com­pon­ent can then de­term­ine the next pro­cess­ing unit(s) to be ex­ecuted. Fol­low­ing this ap­proach, we end up with an al­tern­at­ing pro­cess flow: cent­ral com­pon­ent, pro­cess­ing unit, cent­ral com­pon­ent, pro­cess­ing unit, and so on. As a result, the cent­ral unit re­ceives a mes­sage after each in­di­vidual pro­cess­ing step. When the mes­sage ar­rives, the cent­ral com­pon­ent has to de­term­ine the next pro­cess­ing step(s) to be ex­ecuted based on in­ter­me­di­ate res­ults and the cur­rent step in the se­quence. This would re­quire the in­di­vidual pro­cess­ing units to return suf­fi­cient in­form­a­tion to the cent­ral unit to make this de­cision. How­ever, this ap­proach would make the pro­cess­ing units de­pend­ent on the ex­ist­ence of the cent­ral unit be­cause they might have to pass through ex­traneous in­form­a­tion that is not rel­ev­ant to the pro­cess­ing unit, but only to the cent­ral com­pon­ent. If we want to de­couple the in­di­vidual pro­cess­ing steps and the as­so­ci­ated mes­sage formats from the cent­ral unit, we need to provide the cent­ral unit with some form of "memory" that tells it what step in the se­quence was ex­ecuted last.

                                                                                                                                                                        使用中央处理单元(流程管理器)来维护序列的状态并根据中间结果确定下一个处理步骤。

                                                                                                                                                                        Use a cent­ral pro­cess­ing unit, a Pro­cess Man­ager, to main­tain the state of the se­quence and de­term­ine the next pro­cess­ing step based on in­ter­me­di­ate res­ults.

                                                                                                                                                                        图形/07inf40.gif



                                                                                                                                                                        首先,让我们澄清一下,流程管理器的设计和配置是一个非常广泛的主题。我们可能可以用与工作流或业务流程管理设计相关的模式来写满整本书(也许是第 2 卷?)。因此,该模式的主要目的是“完善”路由模式的主题,并为工作流和流程建模的方向提供指导。它绝不是业务流程设计的综合处理。

                                                                                                                                                                        First of all, let's cla­rify that the design and con­fig­ur­a­tion of a Pro­cess Man­ager is a pretty ex­tens­ive topic. We could prob­ably fill a whole book (Volume 2, maybe?) with pat­terns re­lated to the design of work­flow or busi­ness pro­cess man­age­ment. There­fore, this pat­tern is in­ten­ded primar­ily to "round off" the topic of rout­ing pat­terns and to provide a pointer into the dir­ec­tion of work­flow and pro­cess mod­el­ing. By no means is it a com­pre­hens­ive treat­ment of busi­ness pro­cess design.

                                                                                                                                                                        使用流程管理器会产生所谓的中心辐射型消息流模式(参见图表)。传入消息将初始化流程管理器。我们将此消息称为触发消息。根据流程管理器内部的规则,它向由处理单元 A 实现的第一个处理步骤发送消息 (1)。单元 A 完成其任务后,将回复消息发送回流程。流程经理确定要执行的下一步并向下一个处理单元发送消息(2)。因此,所有消息流量都通过这个中央“中心”,因此称为“中心辐射”。这个中央控制元素的缺点是有将流程管理器变成性能瓶颈的危险。

                                                                                                                                                                        Using a Pro­cess Man­ager res­ults in a so-called hub-and-spoke pat­tern of mes­sage flow (see dia­gram). An in­com­ing mes­sage ini­tial­izes the Pro­cess Man­ager. We call this mes­sage the trig­ger mes­sage. Based on the rules inside the Pro­cess Man­ager, it sends a mes­sage (1) to the first pro­cess­ing step, im­ple­men­ted by Pro­cess­ing Unit A. After unit A com­pletes its task, it sends a reply mes­sage back to the Pro­cess Man­ager. The Pro­cess Man­ager de­term­ines the next step to be ex­ecuted and sends a mes­sage (2) to the next pro­cess­ing unit. As a result, all mes­sage traffic runs through this cent­ral "hub," hence the term hub-and-spoke. The down­side of this cent­ral con­trol ele­ment is the danger of turn­ing the Pro­cess Man­ager into a per­form­ance bot­tle­neck.

                                                                                                                                                                        流程经理的多功能性同时也是其最大的优点和缺点。流程管理器可以执行任何顺序的步骤,顺序或并行。因此,几乎任何集成问题都可以通过流程管理器来解决。 同样,本章介绍的大多数模式都可以使用流程管理器来实现。事实上,许多EAI供应商让你相信每个集成问题都是一个流程问题。我们认为在所有情况下都使用流程管理器可能有点过分了。它可能会分散核心设计问题的注意力,还会导致显着的性能开销。

                                                                                                                                                                        The ver­sat­il­ity of a Pro­cess Man­ager is at the same time its biggest strength and weak­ness. A Pro­cess Man­ager can ex­ecute any se­quence of steps, se­quen­tial or in par­al­lel. There­fore, almost any in­teg­ra­tion prob­lem can be solved with a Pro­cess Man­ager. Like­wise, most of the pat­terns in­tro­duced in this chapter could be im­ple­men­ted using a Pro­cess Man­ager. In fact, many EAI vendors make you be­lieve that every in­teg­ra­tion prob­lem is a pro­cess prob­lem. We think that using a Pro­cess Man­ager for every situ­ation may be overkill. It can dis­tract from the core design issue and also cause sig­ni­fic­ant per­form­ance over­head.

                                                                                                                                                                        管理状态

                                                                                                                                                                        Man­aging State

                                                                                                                                                                        流程管理器的关键功能之一维护消息之间的状态。例如,当第二处理单元向流程管理器返回消息时流程管理器需要记住这是许多步骤序列中的步骤2。我们不想将这些知识与处理单元联系起来,因为同一单元可能会在同一进程中出现多次。例如,处理单元B可以是单个过程的步骤2和步骤4。结果,处理单元B发送的相同回复消息可以触发流程管理器基于流程上下文执行步骤3或步骤5。为了在不使处理单元复杂化的情况下实现这一点,流程管理器需要维护流程执行中的当前位置。

                                                                                                                                                                        One of the key func­tions of the Pro­cess Man­ager is to main­tain state between mes­sages. For ex­ample, when the second pro­cess­ing unit re­turns a mes­sage to the Pro­cess Man­ager, the Pro­cess Man­ager needs to re­mem­ber that this is step 2 out of a se­quence of many steps. We do not want to tie this know­ledge to the pro­cess­ing unit be­cause the same unit may appear mul­tiple times inside the same pro­cess. For ex­ample, Pro­cess­ing Unit B may be both step 2 and step 4 of a single pro­cess. As a result, the same reply mes­sage sent by Pro­cess­ing Unit B may trig­ger the Pro­cess Man­ager to ex­ecute step 3 or step 5 based on the pro­cess con­text. To ac­com­plish this without com­plic­at­ing the pro­cess­ing unit, the Pro­cess Man­ager needs to main­tain the cur­rent po­s­i­tion in the pro­cess ex­e­cu­tion.

                                                                                                                                                                        除了流程中的当前位置之外,流程管理器能够存储其他信息非常有用。如果与后续步骤相关,流程管理可以存储先前处理的中间结果。例如,如果步骤 1 的结果与后面的步骤相关,则流程管理器可以存储此信息,而不会因来回传递此数据而增加后续处理单元的负担。这允许各个处理步骤彼此独立,因为它们不必担心其他单元生成或消耗的数据。实际上,流程经理扮演着索赔检查的角色稍后解释。

                                                                                                                                                                        It is useful for the Pro­cess Man­ager to be able to store ad­di­tional in­form­a­tion be­sides the cur­rent po­s­i­tion in the pro­cess. The Pro­cess Man­ager can store in­ter­me­di­ate res­ults from pre­vi­ous pro­cess­ing if it is rel­ev­ant to later steps. For ex­ample, if the res­ults of step 1 are rel­ev­ant to a later step, the Pro­cess Man­ager can store this in­form­a­tion without bur­den­ing the sub­se­quent pro­cess­ing units with passing this data back and forth. This allows the in­di­vidual pro­cess­ing steps to be in­de­pend­ent of each other be­cause they do not have to worry about data pro­duced or con­sumed by other units. Ef­fect­ively, the Pro­cess Man­ager plays the role of a Claim Check ex­plained later.

                                                                                                                                                                        流程实例

                                                                                                                                                                        Pro­cess In­stances

                                                                                                                                                                        由于流程执行可能跨越许多步骤,因此可能需要很长时间,因此流程管理器需要准备好在另一个流程仍在执行时接收新的触发消息。为了管理多个并行执行,流程管理器为每个传入的触发消息创建一个新的流程实例。流程实例存储与由触发消息启动的流程的执行相关联的状态。状态包括流程的当前执行步骤和任何相关数据。每个流程实例都由唯一的流程标识符来标识。

                                                                                                                                                                        Be­cause the pro­cess ex­e­cu­tion can span many steps and can there­fore take a long time, the Pro­cess Man­ager needs to be pre­pared to re­ceive new trig­ger mes­sages while an­other pro­cess is still ex­ecut­ing. In order to manage mul­tiple par­al­lel ex­e­cu­tions, the Pro­cess Man­ager cre­ates a new pro­cess in­stance for each in­com­ing trig­ger mes­sage. The pro­cess in­stance stores the state as­so­ci­ated with the ex­e­cu­tion of the pro­cess ini­ti­ated by the trig­ger mes­sage. The state in­cludes the cur­rent ex­e­cu­tion step of the pro­cess and any as­so­ci­ated data. Each pro­cess in­stance is iden­ti­fied by a unique pro­cess iden­ti­fier.

                                                                                                                                                                        区分流程定义(也称为流程模板)和流程实例的概念非常重要。流程定义是一种设计构造,它定义了要执行的步骤序列,类似于面向对象语言中的类。流程实例是特定模板的主动执行,类似于 OO 语言中的对象实例。下图显示了一个简单示例,其中包含一个流程定义和两个流程实例。第一个实例(进程标识符 1234)当前正在执行步骤 1,而第二个进程实例(进程标识符 5678)正在并行执行步骤 2 和步骤 5。

                                                                                                                                                                        It is im­port­ant to sep­ar­ate the con­cepts of a pro­cess defin­i­tion (also re­ferred to as pro­cess tem­plate) and a pro­cess in­stance. The pro­cess defin­i­tion is a design con­struct that defines the se­quence of steps to be ex­ecuted, com­par­able to a class in object-ori­ented lan­guages. The pro­cess in­stance is an active ex­e­cu­tion of a spe­cific tem­plate, com­par­able to an object in­stance in an OO lan­guage. The dia­gram below shows a simple ex­ample with one pro­cess defin­i­tion and two pro­cess in­stances. The first in­stance (pro­cess iden­ti­fier 1234) is cur­rently ex­ecut­ing step 1, while the second pro­cess in­stance (pro­cess iden­ti­fier 5678) is ex­ecut­ing steps 2 and 5 in par­al­lel.

                                                                                                                                                                        基于一个流程定义的多个流程实例

                                                                                                                                                                        Mul­tiple Pro­cess In­stances Based on One Pro­cess Defin­i­tion

                                                                                                                                                                        图形/07inf41.gif

                                                                                                                                                                        相关性

                                                                                                                                                                        Cor­rel­a­tion

                                                                                                                                                                        由于多个流程实例可能同时执行,因此流程管理器需要能够将传入消息与正确的实例关联起来。例如,如果前面示例中的流程管理器从处理单元接收到一条消息,那么该消息是针对哪个流程实例的?多个实例可能正在执行同一步骤,因此流程管理器无法从通道名称或消息类型派生实例。将传入消息与流程实例关联起来的要求让我们想起了关联标识符相关标识符允许组件通过在回复消息中存储与请求消息相关联的唯一标识符来将传入回复消息与原始请求相关联。使用此标识符,即使组件发送了多个请求并且回复未按顺序到达,组件也可以将回复与正确的请求相匹配。流程管理需要类似的机制。当流程管理器从处理单元接收到消息时,它必须能够将该消息与将该消息发送到该处理单元的流程实例相关联。流程管理器必须包含关联标识符它发送到处理单元的内部消息。该组件需要在回复消息中返回该标识符作为Correlation Identifier 。如果每个流程实例维护唯一的流程标识符,则它可以使用该标识符作为消息的关联标识符

                                                                                                                                                                        Be­cause mul­tiple pro­cess in­stances may be ex­ecut­ing sim­ul­tan­eously, the Pro­cess Man­ager needs to be able to as­so­ci­ate an in­com­ing mes­sage with the cor­rect in­stance. For ex­ample, if the Pro­cess Man­ager in the pre­vi­ous ex­ample re­ceives a mes­sage from a pro­cess­ing unit, which pro­cess in­stance is the mes­sage meant for? Mul­tiple in­stances may be ex­ecut­ing the same step, so the Pro­cess Man­ager cannot derive the in­stance from the chan­nel name or the type of mes­sage. The re­quire­ment to as­so­ci­ate an in­com­ing mes­sage with a pro­cess in­stance re­minds us of the Cor­rel­a­tion Iden­ti­fier. The Cor­rel­a­tion Iden­ti­fier allows a com­pon­ent to as­so­ci­ate an in­com­ing reply mes­sage with the ori­ginal re­quest by stor­ing a unique iden­ti­fier in the reply mes­sage that cor­rel­ates it to the re­quest mes­sage. Using this iden­ti­fier, the com­pon­ent can match the reply with the cor­rect re­quest even if the com­pon­ent has sent mul­tiple re­quests and the replies arrive out of order. The Pro­cess Man­ager re­quires a sim­ilar mech­an­ism. When the Pro­cess Man­ager re­ceives a mes­sage from a pro­cess­ing unit, it must be able to as­so­ci­ate the mes­sage with the pro­cess in­stance that sent the mes­sage to the pro­cess­ing unit. The Pro­cess Man­ager must in­clude a Cor­rel­a­tion Iden­ti­fier inside mes­sages that it sends to pro­cess­ing units. The com­pon­ent needs to return this iden­ti­fier in the reply mes­sage as a Cor­rel­a­tion Iden­ti­fier. If each pro­cess in­stance main­tains a unique pro­cess iden­ti­fier, it can use that iden­ti­fier as a Cor­rel­a­tion Iden­ti­fier for mes­sages.

                                                                                                                                                                        保持消息状态

                                                                                                                                                                        Keep­ing State in Mes­sages

                                                                                                                                                                        显然,状态管理是流程管理器的一个重要特性。那么,以前的模式是如何在不管理状态的情况下摆脱困境的呢?在传统的管道和过滤器架构中,管道(即消息通道)管理状态。继续前面的示例,如果我们要使用通过消息通道连接的硬连线组件来实现该流程,它看起来如下图所示。如果我们假设该系统与前面的示例处于相同的状态(即,有两个流程实例),则它相当于一条标识符为 1234 的消息位于通道中等待组件 1 处理,另外两条消息的标识符为 1234 5678分别等待组件2和5处理。一旦组件 1 使用该消息并完成其处理任务,它就会向组件 2 和 4 广播一条新消息,这与上一示例中的流程管理器的行为完全相同。

                                                                                                                                                                        It is ap­par­ent that state man­age­ment is an im­port­ant fea­ture of the Pro­cess Man­ager. How, then, did the pre­vi­ous pat­terns get away without man­aging state? In a tra­di­tional Pipes and Fil­ters ar­chi­tec­ture, the pipes (i.e., the Mes­sage Chan­nels ) manage the state. To con­tinue the pre­vi­ous ex­ample, if we were to im­ple­ment the pro­cess with hard-wired com­pon­ents con­nec­ted by Mes­sage Chan­nels, it would look like the fol­low­ing figure. If we assume that this system is in the same state as the pre­vi­ous ex­ample (i.e., has two pro­cess in­stances), it equates to one mes­sage with the iden­ti­fier 1234 sit­ting in a chan­nel wait­ing to be pro­cessed by com­pon­ent 1 and two mes­sages with the iden­ti­fier 5678 wait­ing to be pro­cessed by the com­pon­ents 2 and 5 re­spect­ively. As soon as com­pon­ent 1 con­sumes the mes­sage and com­pletes its pro­cess­ing tasks, it broad­casts a new mes­sage to com­pon­ents 2 and 4ex­actly the same be­ha­vior as the Pro­cess Man­ager in the pre­vi­ous ex­ample.

                                                                                                                                                                        保持通道状态

                                                                                                                                                                        Keep­ing State in Chan­nels

                                                                                                                                                                        图形/07inf42.gif

                                                                                                                                                                        令人惊讶的是,此示例中使用的消息流表示法与 UML 活动图非常相似,而 UML 活动图通常用于对Process Manager组件的行为进行建模。实际上,我们可以在设计过程中使用抽象符号对系统的行为进行建模,然后决定是否要将行为实现为分布式管道和过滤器架构,还是使用中央流程管理器的中心辐射型架构。尽管本书没有足够的篇幅来深入探讨流程模型的设计,但这种语言中的许多模式在设计流程模型时确实适用。

                                                                                                                                                                        It is strik­ing how much the mes­sage flow nota­tion used for this ex­ample re­sembles a UML activ­ity dia­gram that is often used to model the be­ha­vior of Pro­cess Man­ager com­pon­ents. Ef­fect­ively, we can use an ab­stract nota­tion to model the be­ha­vior of the system during design and then decide whether we want to im­ple­ment the be­ha­vior as a dis­trib­uted Pipes and Fil­ters ar­chi­tec­ture or as a hub-and-spoke ar­chi­tec­ture using a cent­ral Pro­cess Man­ager. Even though we don't have room in this book to dive too deeply into the design of pro­cess models, many of the pat­terns in this lan­guage do apply when design­ing a pro­cess model.

                                                                                                                                                                        与大多数架构决策一样,实施中央流程管理器或分布式管道和过滤器架构并不是简单的是或否的决定。在许多情况下,使用多个Process Manager组件是最有意义的,每个组件都包含较大流程的特定方面。然后,流程管理器组件可以通过管道和过滤器架构相互通信。

                                                                                                                                                                        As with most ar­chi­tec­tural de­cisions, im­ple­ment­ing a cent­ral Pro­cess Man­ager or a dis­trib­uted Pipes and Fil­ters ar­chi­tec­ture is not a simple yes or no de­cision. In many cases, it makes most sense to use mul­tiple Pro­cess Man­ager com­pon­ents, each of which houses a par­tic­u­lar aspect of a larger pro­cess. The Pro­cess Man­ager com­pon­ents can then com­mu­nic­ate with each other through a Pipes and Fil­ters ar­chi­tec­ture.

                                                                                                                                                                        在流程管理器内显式管理状态可能需要更复杂的组件,但它允许更强大的流程报告。例如,流程管理器的大多数实现都提供查询流程实例状态的能力。这样可以轻松查看当前有多少订单正在等待批准或因库存不足而被搁置。我们还可以告诉每个客户他或她的订单状态。如果我们使用硬连线通道,我们将必须检查所有通道以获得相同的信息。进程管理器的这个属性不仅对于报告很重要,而且对于调试也很重要。使用中央流程管理器可以轻松检索进程的当前状态和关联数据。调试完全分布式的体系结构可能会更具挑战性,并且如果没有消息历史记录或消息存储等机制的帮助,几乎是不可能的

                                                                                                                                                                        Man­aging state ex­pli­citly inside a Pro­cess Man­ager may re­quire a more com­plex com­pon­ent, but it allows much more power­ful pro­cess re­port­ing. For ex­ample, most im­ple­ment­a­tions of a Pro­cess Man­ager provide the abil­ity to query pro­cess in­stance state. This makes it easy to see how many orders are cur­rently wait­ing for ap­proval or have been put on hold be­cause of lack­ing in­vent­ory. We can also tell each cus­tomer the status of his or her order. If we used hard-wired chan­nels, we would have to in­spect all chan­nels to obtain the same in­form­a­tion. This prop­erty of the Pro­cess Man­ager is not only im­port­ant for re­port­ing, but also for de­bug­ging. Using a cent­ral Pro­cess Man­ager makes it easy to re­trieve the cur­rent state of a pro­cess and the as­so­ci­ated data. De­bug­ging a fully dis­trib­uted ar­chi­tec­ture can be a lot more chal­len­ging and is almost im­pos­sible without the as­sist­ance of such mech­an­isms as the Mes­sage His­tory or Mes­sage Store.

                                                                                                                                                                        创建流程定义

                                                                                                                                                                        Cre­at­ing the Pro­cess Defin­i­tion

                                                                                                                                                                        大多数商业 EAI 实施都包括流程管理器组件和可视化工具,用于对流程定义进行建模。大多数可视化工具使用类似于 UML 活动图的表示法,因为流程管理器的语义和活动图非常相似。此外,活动图是并行执行的多个任务的良好可视化表示。直到最近,大多数供应商工具都将视觉符号转换为供应商专有的内部流程定义,以由流程引擎执行。然而,在 Web 服务的保护下推动分布式系统各个方面的标准化并没有忽视流程定义的重要作用。这些努力的结果出现了三种拟议的“语言”。Microsoft 定义了 XLANG,并得到其 BizTalk 编排建模工具系列的支持。IBM 起草了 WSFL,即 Web 服务流语言 [ WSFL]。最近,两家公司联手创建了 BPEL4WS 规范,即 Web 服务的业务流程执行语言(请参阅 [ BPEL4WS ])。BPEL4WS 是一种功能强大的语言,它将流程模型描述为 XML 文档。目的是定义流程建模工具和流程管理器引擎之间的标准化中间语言。这样,我们可以使用供应商 X 的产品对我们的流程进行建模,并决定在供应商 Y 的流程引擎实现上执行该流程。有关 Web 服务标准对集成的影响的更多信息,请参阅第 14中的“企业集成中的新兴标准和未来”

                                                                                                                                                                        Most com­mer­cial EAI im­ple­ment­a­tions in­clude a Pro­cess Man­ager com­pon­ent com­bined with visual tools to model the pro­cess defin­i­tion. Most visual tools use a nota­tion that re­sembles UML activ­ity dia­grams be­cause the se­mantics of a Pro­cess Man­ager and those of an activ­ity dia­gram are fairly sim­ilar. Also, activ­ity dia­grams are a good visual rep­res­ent­a­tion of mul­tiple tasks ex­ecut­ing in par­al­lel. Until re­cently, most vendor tools con­ver­ted the visual nota­tion into an in­ternal, vendor-pro­pri­et­ary pro­cess defin­i­tion to be ex­ecuted by the pro­cess engine. How­ever, the push to stand­ard­ize vari­ous as­pects of dis­trib­uted sys­tems under the um­brella of Web ser­vices has not ig­nored the im­port­ant role of pro­cess defin­i­tions. Three pro­posed "lan­guages" have emerged as a result of these ef­forts. Mi­crosoft defined XLANG, which is sup­por­ted by its family of BizTalk or­ches­tra­tion mod­el­ing tools. IBM draf­ted the WSFL, the Web Ser­vices Flow Lan­guage [WSFL]. Re­cently, both com­pan­ies have joined forces to create the spe­cific­a­tion for BPEL4WS, the Busi­ness Pro­cess Ex­e­cu­tion Lan­guage for Web Ser­vices (see [BPEL4WS]). The BPEL4WS is a power­ful lan­guage that de­scribes a pro­cess model as an XML doc­u­ment. The intent is to define a stand­ard­ized in­ter­me­di­ate lan­guage between the pro­cess mod­el­ing tools and the Pro­cess Man­ager en­gines. This way, we could model our pro­cesses with Vendor X's product and decide to ex­ecute the pro­cess on Vendor Y's pro­cess engine im­ple­ment­a­tion. For more in­form­a­tion on the impact of Web ser­vices stand­ards on in­teg­ra­tion, see "Emer­ging Stand­ards and Fu­tures in En­ter­prise In­teg­ra­tion" in Chapter 14.

                                                                                                                                                                        流程定义的语义可以用相当简单的术语来描述。基本构建块是活动(有时称为任务或操作)。通常,活动可以向另一个组件发送消息、等待传入消息或在内部执行特定功能(例如,消息转换器)。活动可以以串行方式连接,也可以使用 fork 和 join 构造并行执行多个活动。分叉允许同时执行多个活动。它在语义上相当于硬连线管道和过滤器中的发布-订阅通道建筑学。连接将多个并行执行线程同步回单个线程。仅当所有并行线程都完成了各自的活动时,连接后的执行才能继续。在管道和过滤器风格中,聚合器通常用于此目的。流程模板还必须能够指定分支或决策点,以便执行路径可以根据消息字段的内容进行更改。这个功能相当于一个Content-Based Router。许多建模工具都包含设计循环构造的功能,但这实际上是分支的一个特例。下图突出显示了流程定义(描述为 UML 活动图)与使用此模式语言中定义的模式的管道和过滤器实现之间的语义相似性,尽管物理实现非常不同。

                                                                                                                                                                        The se­mantics of a pro­cess defin­i­tion can be de­scribed in rather simple terms. The basic build­ing block is an activ­ity (some­times called task or action). Usu­ally, an activ­ity can send a mes­sage to an­other com­pon­ent, wait for an in­com­ing mes­sage, or ex­ecute a spe­cific func­tion in­tern­ally (e.g., a Mes­sage Trans­lator ). Activ­it­ies can be con­nec­ted in serial fash­ion, or mul­tiple activ­it­ies can be ex­ecuted in par­al­lel using a fork and join con­struct. A fork allows mul­tiple activ­it­ies to ex­ecute at the same time. It is se­mantic­ally equi­val­ent to a Pub­lish-Sub­scribe Chan­nel in a hard-wired Pipes and Fil­ters ar­chi­tec­ture. A join syn­chron­izes mul­tiple par­al­lel threads of ex­e­cu­tion back into a single thread. Ex­e­cu­tion after a join can con­tinue only if all par­al­lel threads have com­pleted their re­spect­ive activ­it­ies. In the Pipes and Fil­ters style, an Ag­greg­ator often serves this pur­pose. The pro­cess tem­plate also must be able to spe­cify a branch, or de­cision point, so that the path of ex­e­cu­tion can change based on the con­tent of a mes­sage field. This func­tion is equi­val­ent to a Con­tent-Based Router. Many mod­el­ing tools in­clude the abil­ity to design a loop con­struct, but this is really a spe­cial case of the branch. The fol­low­ing figure high­lights the se­mantic sim­il­ar­it­ies between a pro­cess defin­i­tion (de­pic­ted as a UML activ­ity dia­gram) and a Pipes and Fil­ters im­ple­ment­a­tion using the pat­terns defined in this pat­tern lan­guage, even though the phys­ical im­ple­ment­a­tions are very dif­fer­ent.

                                                                                                                                                                        示例 UML 活动图和相应的管道和过滤器实现

                                                                                                                                                                        Ex­ample UML Activ­ity Dia­gram and Cor­res­pond­ing Pipes-and-Fil­ters Im­ple­ment­a­tion

                                                                                                                                                                        图形/07inf43.gif

                                                                                                                                                                        将流程管理器与其他模式进行比较

                                                                                                                                                                        Com­par­ing the Pro­cess Man­ager against Other Pat­terns

                                                                                                                                                                        我们已经多次对比了基本的管道和过滤器架构、路由表流程管理器。我们将这些模式之间的主要差异整理到下表中,以突出显示选择正确架构时涉及的权衡。

                                                                                                                                                                        We have con­tras­ted a basic Pipes and Fil­ters ar­chi­tec­ture, the Rout­ing Slip, and the Pro­cess Man­ager sev­eral times. We com­piled the key dif­fer­ences between these pat­terns into the fol­low­ing table to high­light the trade-offs in­volved in choos­ing the cor­rect ar­chi­tec­ture.

                                                                                                                                                                        分布式管道和过滤器

                                                                                                                                                                        Dis­trib­uted Pipes and Fil­ters

                                                                                                                                                                        路由表

                                                                                                                                                                        Rout­ing Slip

                                                                                                                                                                        中央流程管理器

                                                                                                                                                                        Cent­ral Pro­cess Man­ager

                                                                                                                                                                        支持复杂的消息流

                                                                                                                                                                        Sup­ports com­plex mes­sage flow

                                                                                                                                                                        仅支持简单的线性流

                                                                                                                                                                        Sup­ports only simple, linear flow

                                                                                                                                                                        支持复杂的消息流

                                                                                                                                                                        Sup­ports com­plex mes­sage flow

                                                                                                                                                                        改变流量困难

                                                                                                                                                                        Dif­fi­cult to change flow

                                                                                                                                                                        易于改变流量

                                                                                                                                                                        Easy to change flow

                                                                                                                                                                        易于改变流量

                                                                                                                                                                        Easy to change flow

                                                                                                                                                                        无中心故障点

                                                                                                                                                                        No cent­ral point of fail­ure

                                                                                                                                                                        潜在故障点(计算路由表)

                                                                                                                                                                        Po­ten­tial point of fail­ure (com­pute rout­ing table)

                                                                                                                                                                        潜在的故障点

                                                                                                                                                                        Po­ten­tial point of fail­ure

                                                                                                                                                                        高效的分布式运行时架构

                                                                                                                                                                        Ef­fi­cient dis­trib­uted runtime ar­chi­tec­ture

                                                                                                                                                                        大部分是分布式的

                                                                                                                                                                        Mostly dis­trib­uted

                                                                                                                                                                        中心辐射型架构可能会导致瓶颈

                                                                                                                                                                        Hub-and-spoke ar­chi­tec­ture may lead to bot­tle­neck

                                                                                                                                                                        没有管理和报告的中心点

                                                                                                                                                                        No cent­ral point of ad­min­is­tra­tion and re­port­ing

                                                                                                                                                                        管理中心点,但不报告

                                                                                                                                                                        Cent­ral point of ad­min­is­tra­tion, but not re­port­ing

                                                                                                                                                                        管理和报告的中心点

                                                                                                                                                                        Cent­ral point of ad­min­is­tra­tion and re­port­ing

                                                                                                                                                                        控制和状态管理的中心点也可能意味着故障中心点或性能瓶颈。因此,大多数流程管理器实现都允许在文件或数据库中持久存储流程实例状态。然后,该实现可以利用通常在企业级数据库系统中实现的冗余数据存储机制。并行运行多个Process Manager也很常见。并行进程管理器通常很容易,因为流程实例彼此独立。这允许我们跨多个流程引擎分发流程实例。如果流程引擎将所有状态信息保存在共享数据库中,则系统可以变得足够健壮,能够在流程引擎发生故障时继续存在,而另一个引擎可以简单地从前一个引擎停止的地方继续工作。这种方法的缺点是每个处理步骤之后每个流程实例的状态都必须保存在中央数据库中。这很容易将数据库变成新的性能瓶颈。正如经常发生的那样,架构师必须在性能、稳健性、成本和可维护性之间找到正确的平衡。

                                                                                                                                                                        A cent­ral point of con­trol and state man­age­ment can also mean a cent­ral point of fail­ure or a per­form­ance bot­tle­neck. For this reason, most Pro­cess Man­ager im­ple­ment­a­tions allow per­sist­ent stor­age of pro­cess in­stance state in a file or in a data­base. The im­ple­ment­a­tion can then lever­age re­dund­ant data stor­age mech­an­isms typ­ic­ally im­ple­men­ted in en­ter­prise-class data­base sys­tems. It is also common to run mul­tiple Pro­cess Man­agers in par­al­lel. Par­al­lel­iz­ing Pro­cess Man­agers is gen­er­ally easy be­cause pro­cess in­stances are in­de­pend­ent from each other. This allows us to dis­trib­ute pro­cess in­stances across mul­tiple pro­cess en­gines. If the pro­cess engine per­sists all state in­form­a­tion in a shared data­base, the system can become robust enough to sur­vive the fail­ure of a pro­cess en­ginean­other engine can simply pick up where the pre­vi­ous one left off. The down­side of this ap­proach is that the state of each pro­cess in­stance has to be per­sisted in a cent­ral data­base after each pro­cess­ing step. This could easily turn the data­base into a new per­form­ance bot­tle­neck. As so often hap­pens, the ar­chi­tect has to find the cor­rect bal­ance between per­form­ance, ro­bust­ness, cost, and main­tain­ab­il­ity.

                                                                                                                                                                        示例: 贷款经纪人

                                                                                                                                                                        Ex­ample: Loan Broker

                                                                                                                                                                        贷款经纪人示例的 MSMQ 实现(请参阅第 9 章中的“ MSMQ 异步实现” )实现了一个简单的流程管理器。该示例通过为流程管理器和流程实例编写 C# 类,从头开始创建流程管理器功能。同一示例的 TIBCO 实现(请参阅第 9中的“使用 TIBCO ActiveEnterprise 进行异步实现” )使用商业流程管理工具。

                                                                                                                                                                        The MSMQ im­ple­ment­a­tion of the Loan Broker ex­ample (see "Asyn­chron­ous Im­ple­ment­a­tion with MSMQ" in Chapter 9) im­ple­ments a simple Pro­cess Man­ager. The ex­ample cre­ates the Pro­cess Man­ager func­tion­al­ity from scratch by coding C# classes for both the pro­cess man­ager and pro­cess in­stances. The TIBCO im­ple­ment­a­tion of the same ex­ample (see "Asyn­chron­ous Im­ple­ment­a­tion with TIBCO Act­iveEn­ter­prise" in Chapter 9) uses a com­mer­cial pro­cess man­age­ment tool.



                                                                                                                                                                        示例: Microsoft BizTalk 编排管理器

                                                                                                                                                                        Ex­ample: Mi­crosoft BizTalk Or­ches­tra­tion Man­ager

                                                                                                                                                                        大多数商业 EAI 工具都包含流程设计和执行功能。例如,Microsoft BizTalk 允许用户通过集成到 Visual Studio .NET 编程环境中的 Orchestration Designer 工具来设计流程定义。

                                                                                                                                                                        Most com­mer­cial EAI tools in­clude pro­cess design and ex­e­cu­tion cap­ab­il­it­ies. For ex­ample, Mi­crosoft BizTalk lets users design pro­cess defin­i­tions via the Or­ches­tra­tion De­signer tool that is in­teg­rated into the Visual Studio .NET pro­gram­ming en­vir­on­ment.

                                                                                                                                                                        Microsoft BizTalk 2004 编排设计器

                                                                                                                                                                        Mi­crosoft BizTalk 2004 Or­ches­tra­tion De­signer

                                                                                                                                                                        图形/07inf44.gif

                                                                                                                                                                        这个简单的示例编排接收订单消息并执行两个并行活动。一个活动创建到库存系统的请求消息,另一个活动创建到信用系统的请求消息。一旦收到两个响应,该过程就会继续。视觉符号使遵循流程定义变得容易。

                                                                                                                                                                        This simple ex­ample or­ches­tra­tion re­ceives an order mes­sage and ex­ecutes two par­al­lel activ­it­ies. One activ­ity cre­ates a re­quest mes­sage to the in­vent­ory sys­tems, and the other activ­ity cre­ates a re­quest mes­sage to the credit system. Once both re­sponses are re­ceived, the pro­cess con­tin­ues. The visual nota­tion makes it easy to follow the pro­cess defin­i­tion.



                                                                                                                                                                          消息代理

                                                                                                                                                                          Message Broker

                                                                                                                                                                          图形/messagebroker_icon.gif

                                                                                                                                                                          本章中的许多模式提供了将消息路由到正确目的地的方法,而原始应用程序不知道消息的最终目的地。大多数模式都专注于特定类型的路由逻辑。然而,总的来说,这些模式解决了一个更大的问题。

                                                                                                                                                                          Many pat­terns in this chapter present ways to route mes­sages to the proper des­tin­a­tion without the ori­gin­at­ing ap­plic­a­tion being aware of the ul­ti­mate des­tin­a­tion of the mes­sage. Most of the pat­terns focus on spe­cific types of rout­ing logic. How­ever, in ag­greg­ate, these pat­terns solve a bigger prob­lem.

                                                                                                                                                                          如何将消息的目的地与发送者分离并保持对消息流的集中控制?

                                                                                                                                                                          How can you de­couple the des­tin­a­tion of a mes­sage from the sender and main­tain cent­ral con­trol over the flow of mes­sages?



                                                                                                                                                                          使用简单的消息通道已经在发送者和接收者之间提供了一定程度的间接性——发送者只知道通道的信息,而不知道接收者的信息。然而,如果每个接收器都有自己的通道,那么这种间接级别就变得没有意义。发送方必须知道与接收方关联的正确通道名称,而不是知道接收方的地址。

                                                                                                                                                                          Using a simple Mes­sage Chan­nel already provides a level of in­dir­ec­tion between sender and re­ceiv­erthe sender knows only about the chan­nel but not about the re­ceiver. How­ever, if each re­ceiver has its own chan­nel, this level of in­dir­ec­tion be­comes less mean­ing­ful. In­stead of know­ing the re­ceiver's ad­dress, the sender has to know the cor­rect chan­nel name that is as­so­ci­ated with the re­ceiver.

                                                                                                                                                                          除了最简单的消息传递解决方案之外,所有解决方案都连接许多不同的应用程序。如果我们创建单独的消息通道来将每个应用程序连接到其他应用程序,系统中的通道数量将很快激增到难以管理的数量,从而导致集成意大利面条(见图)。

                                                                                                                                                                          All but the most trivial mes­saging solu­tions con­nect a number of dif­fer­ent ap­plic­a­tions. If we cre­ated in­di­vidual mes­sage chan­nels to con­nect each ap­plic­a­tion to each other ap­plic­a­tion, the chan­nels in the system would quickly ex­plode into an un­man­age­able number, res­ult­ing in in­teg­ra­tion spa­ghetti (see figure).

                                                                                                                                                                          点对点连接造成的集成意大利面条

                                                                                                                                                                          In­teg­ra­tion Spa­ghetti as a Result of Point-to-Point Con­nec­tions

                                                                                                                                                                          图形/07inf45.gif

                                                                                                                                                                          该图说明了各个应用程序之间的直接通道可能会导致通道数量激增,并首先减少通过通道路由消息的许多好处。这些类型的集成架构通常是随着时间的推移不断发展的解决方案的结果。首先,客户服务系统必须与会计系统对话。然后,客户服务系统还需要从库存系统中检索信息,而运输系统则需要使用运输费用来更新会计系统。很容易看出“再添加一件”会如何迅速损害解决方案的整体完整性。

                                                                                                                                                                          This dia­gram il­lus­trates that direct chan­nels between in­di­vidual ap­plic­a­tions can lead to an ex­plo­sion of the number of chan­nels and reduce many of the be­ne­fits of rout­ing mes­sages through chan­nels in the first place. These types of in­teg­ra­tion ar­chi­tec­tures are often a result of a solu­tion that grew over time. First, the cus­tomer care system had to talk to the ac­count­ing system. Then, the cus­tomer care system was also ex­pec­ted to re­trieve in­form­a­tion from the in­vent­ory system, and the ship­ping system was to update the ac­count­ing system with the ship­ping charges. It is easy to see how "adding one more piece" can quickly com­prom­ise the over­all in­teg­rity of solu­tion.

                                                                                                                                                                          要求应用程序与所有其他应用程序显式通信可能会很快妨碍系统的可维护性。例如,如果客户服务系统中的客户地址发生变化,则该系统必须向维护客户地址副本的所有系统发送消息。每次添加新系统时,客户服务系统都必须知道该系统是否使用地址并进行相应的更改。

                                                                                                                                                                          Re­quir­ing an ap­plic­a­tion to ex­pli­citly com­mu­nic­ate with all other ap­plic­a­tions can quickly hamper the main­tain­ab­il­ity of the system. For ex­ample, if the cus­tomer ad­dress changes in the cus­tomer care system, this system would have to send a mes­sage to all sys­tems that main­tain copies of the cus­tomer ad­dress. Every time a new system is added, the cus­tomer care system would have to know whether that system uses ad­dresses and be changed ac­cord­ingly.

                                                                                                                                                                          发布-订阅通道提供这适用于简单的广播场景,但路由规则通常要复杂得多。例如,传入的订单消息可能必须根据订单的大小或性质路由到不同的系统。为了避免让应用程序负责确定消息的最终目的地,中间件应该包括一个可以将消息路由到适当目的地的消息路由器。

                                                                                                                                                                          Pub­lish-Sub­scribe Chan­nels provide some form of basic rout­ingthe mes­sage is routed to each ap­plic­a­tion that sub­scribed to the spe­cific chan­nel. This works in simple broad­cast scen­arios, but rout­ing rules often are much more com­plic­ated. For ex­ample, an in­com­ing order mes­sage may have to be routed to a dif­fer­ent system based on the size or the nature of the order. To avoid making the ap­plic­a­tions re­spons­ible for de­term­in­ing a mes­sage's ul­ti­mate des­tin­a­tion, the mid­dle­ware should in­clude a Mes­sage Router that can route mes­sages to the ap­pro­pri­ate des­tin­a­tion.

                                                                                                                                                                          单独的消息路由模式帮助我们将发送者与接收者解耦。例如,收件人列表可以帮助将有关所有收件人的信息从发件人处提取到中间件层。将逻辑移至中间件层对我们有两个帮助。首先,许多商业中间件和 EAI 套件提供专门用于执行此类任务的工具和库。这简化了编码工作,因为我们不必编写消息端点相关的代码,例如事件驱动的消费者或线程管理。此外,在中间件层内部实现逻辑使我们能够使逻辑比应用程序内部的实际逻辑“更智能”。例如,当新系统添加到集成解决方案时,使用动态收件人列表可以避免编码更改。

                                                                                                                                                                          In­di­vidual mes­sage rout­ing pat­terns have helped us de­couple the sender from the re­ceiver(s). For ex­ample, a Re­cip­i­ent List can help pull the know­ledge about all re­cip­i­ents out of the sender and into the mid­dle­ware layer. Moving the logic into the mid­dle­ware layer helps us in two ways. First, many of the com­mer­cial mid­dle­ware and EAI suites provide tools and lib­rar­ies that are spe­cial­ized to per­form these kinds of tasks. This sim­pli­fies the coding effort be­cause we do not have to write the Mes­sage En­d­point re­lated code, such as Event-Driven Con­sumers or thread man­age­ment. Also, im­ple­ment­ing the logic inside the mid­dle­ware layer allows us to make the logic "smarter" than would be prac­tical inside of the ap­plic­a­tion. For ex­ample, using a dy­namic Re­cip­i­ent List can avoid coding changes when new sys­tems are added to the in­teg­ra­tion solu­tion.

                                                                                                                                                                          然而,拥有大量单独的消息路由器组件几乎与我们试图解决的集成意大利面条一样难以管理。

                                                                                                                                                                          How­ever, having a large number of in­di­vidual Mes­sage Router com­pon­ents can be almost as hard to manage as the in­teg­ra­tion spa­ghetti we were trying to re­solve.

                                                                                                                                                                          使用中央消息代理,它可以从多个目标接收消息、确定正确的目标并将消息路由到正确的通道。使用其他消息路由器实现消息代理的内部结构。

                                                                                                                                                                          Use a cent­ral Mes­sage Broker that can re­ceive mes­sages from mul­tiple des­tin­a­tions, de­term­ine the cor­rect des­tin­a­tion, and route the mes­sage to the cor­rect chan­nel. Im­ple­ment the in­tern­als of the Mes­sage Broker using other mes­sage routers.

                                                                                                                                                                          图形/07inf46.gif



                                                                                                                                                                          使用中央消息代理有时被称为中心辐射型架构风格,从上图来看,这似乎是一个描述性名称。

                                                                                                                                                                          Using a cent­ral Mes­sage Broker is some­times re­ferred to as hub-and-spoke ar­chi­tec­tural style, which ap­pears to be a de­script­ive name when look­ing at the figure above.

                                                                                                                                                                          消息代理模式的范围与本章中介绍的大多数其他模式略有不同。它是一种架构模式,而不是单独的设计模式。因此,它类似于管道和过滤器架构风格,它为我们提供了一种将组件链接在一起以形成更复杂消息流的基本方法。消息代理不仅仅是链接各个组件,它还关注更大的解决方案,并帮助我们处理管理此类系统不可避免的复杂性。

                                                                                                                                                                          The Mes­sage Broker pat­tern has a slightly dif­fer­ent scope than most of the other pat­terns presen­ted in this chapter. It is an ar­chi­tec­ture pat­tern as op­posed to in­di­vidual design pat­tern. As such, it is com­par­able to the Pipes and Fil­ters ar­chi­tec­tural style, which gives us a fun­da­mental way of chain­ing com­pon­ents to­gether to form more com­plex mes­sage flows. Rather than just chain­ing in­di­vidual com­pon­ents, the Mes­sage Broker con­cerns itself with larger solu­tions and helps us deal with the in­ev­it­able com­plex­ity of man­aging such a system.

                                                                                                                                                                          Message Broker不是一个整体组件。在内部,它使用了本章中介绍的许多消息路由模式。因此,一旦您决定使用Message Broker作为架构模式,您就可以选择正确的Message Router设计模式来实现Message Broker

                                                                                                                                                                          The Mes­sage Broker is not a mono­lithic com­pon­ent. In­tern­ally, it uses many of the mes­sage rout­ing pat­terns presen­ted in this chapter. So, once you decide to use the Mes­sage Broker as an ar­chi­tec­tural pat­tern, you can choose the cor­rect Mes­sage Router design pat­terns to im­ple­ment the Mes­sage Broker.

                                                                                                                                                                          消息代理集中维护的优点也可能变成缺点。通过单个Message Broker路由所有消息可能会将Message Broker变成严重的瓶颈。许多技术可以帮助我们缓解这个问题。例如,消息代理模式只告诉我们开发一个执行路由的实体。它没有规定我们在部署时在系统中部署多少个该实体的实例。如果消息代理设计是无状态的(即,如果它仅由无状态组件组成),我们可以轻松部署代理的多个实例以提高吞吐量。点对点通道的属性可确保只有一个Message Broker 实例使用任何传入消息。此外,与大多数现实生活中的情况一样,最终的解决方案最终是模式的组合。同样,在许多复杂的集成解决方案中,设计多个Message Broker组件可能是有意义的,每个组件专门负责解决方案的特定部分。这可以避免创建 uber- Message Broker这太复杂了,以至于无法维护。明显的另一面是,我们不再有单点维护,并且可以创建一种新形式的Message Broker意大利面。一种使用Message Broker组合的优秀架构风格是Message Broker 层次结构(见图)。此配置类似于由各个子网组成的网络配置。如果消息仅需要在子网内的两个应用程序之间传输,则本地Message Broker可以管理消息的路由。如果消息的目的地是另一个子网,则本地Message Broker可以将消息传递到中央Message Broker,然后决定最终目的地。中央Message Broker执行与本地Message Broker相同的功能,但它不是解耦单个应用程序,而是解耦由多个应用程序组成的整个子系统。

                                                                                                                                                                          The ad­vant­age of cent­ral main­ten­ance of a Mes­sage Broker can also turn into a dis­ad­vant­age. Rout­ing all mes­sages through a single Mes­sage Broker can turn the Mes­sage Broker into a ser­i­ous bot­tle­neck. A number of tech­niques can help us al­le­vi­ate this prob­lem. For ex­ample, the Mes­sage Broker pat­tern only tells us to de­velop a single entity that per­forms rout­ing. It does not pre­scribe how many in­stances of this entity we deploy in the system at de­ploy­ment time. If the Mes­sage Broker design is state­less (i.e., if it is com­posed only of state­less com­pon­ents), we can easily deploy mul­tiple in­stances of the broker to im­prove through­put. The prop­er­ties of a Point-to-Point Chan­nel ensure that only one in­stance of the Mes­sage Broker con­sumes any in­com­ing mes­sage. Also, as in most real-life situ­ations, the ul­ti­mate solu­tion ends up being a com­bin­a­tion of pat­terns. Like­wise, in many com­plex in­teg­ra­tion solu­tions, it may make sense to design mul­tiple Mes­sage Broker com­pon­ents, each spe­cial­iz­ing in a spe­cific por­tion of the solu­tion. This avoids cre­at­ing the über-Mes­sage Broker that is so com­plex as to become un­main­tain­able. The ap­par­ent flip-side is that we no longer have a single point of main­ten­ance and could create a new form of Mes­sage Broker spa­ghetti. One ex­cel­lent ar­chi­tec­tural style that uses a com­bin­a­tion of Mes­sage Brokers is a Mes­sage Broker hier­archy (see figure). This con­fig­ur­a­tion re­sembles a net­work con­fig­ur­a­tion com­posed out of in­di­vidual sub­nets. If a mes­sage has to travel only between two ap­plic­a­tions inside a subnet, the local Mes­sage Broker can manage the rout­ing of the mes­sage. If the mes­sage is destined for an­other subnet, the local Mes­sage Broker can pass the mes­sage to the cent­ral Mes­sage Broker, which then de­term­ines the ul­ti­mate des­tin­a­tion. The cent­ral Mes­sage Broker per­forms the same func­tions as a local Mes­sage Broker, but in­stead of de­coup­ling in­di­vidual ap­plic­a­tions, it de­couples whole sub­sys­tems con­sist­ing of mul­tiple ap­plic­a­tions.

                                                                                                                                                                          消息代理的层次结构提供了解耦,同时避免了超级代理

                                                                                                                                                                          A Hier­archy of Mes­sage Brokers Provides De­coup­ling While Avoid­ing the Über-Broker

                                                                                                                                                                          图形/07inf47.gif

                                                                                                                                                                          由于消息代理的目的是减少各个应用程序之间的耦合,因此它通常必须处理应用程序之间的消息数据格式转换。如果发送应用程序必须以(假定隐藏的)目的地的消息格式来格式化消息,那么让消息代理抽象消息的路由对发送应用程序没有任何帮助。下一章将介绍一系列消息转换模式来解决这些问题。在许多情况下,消息代理在内部使用规范数据模型来避免N-平方问题(系统中每个接收者之间进行翻译所需的翻译人员数量随着参与者数量的平方而增长)。

                                                                                                                                                                          Be­cause the pur­pose of the Mes­sage Broker is to reduce coup­ling between in­di­vidual ap­plic­a­tions, it usu­ally has to deal with trans­lat­ing mes­sage data formats between ap­plic­a­tions. Having a Mes­sage Broker ab­stract the rout­ing of the mes­sage does not help the send­ing ap­plic­a­tion if it has to format the mes­sage in the (sup­posedly hidden) des­tin­a­tion's mes­sage format. The next chapter in­tro­duces a series of mes­sage trans­form­a­tion pat­terns to ad­dress these issues. In many cases, a Mes­sage Broker uses a Ca­non­ical Data Model in­tern­ally to avoid the N-square prob­lem (the number of trans­lat­ors re­quired to trans­late between each and every re­cip­i­ent in a system grows with the square of the number of par­ti­cipants).

                                                                                                                                                                          示例: 商业 EAI 工具

                                                                                                                                                                          Ex­ample: Com­mer­cial EAI Tools

                                                                                                                                                                          大多数商业 EAI 套件提供的工具可以大大简化集成解决方案的Message Broker组件的创建。这些工具套件通常提供许多支持Message Broker的开发和部署的功能:

                                                                                                                                                                          Most com­mer­cial EAI suites provide tools to greatly sim­plify the cre­ation of Mes­sage Broker com­pon­ents for in­teg­ra­tion solu­tions. These tool suites typ­ic­ally provide a number of fea­tures that sup­port the de­vel­op­ment and de­ploy­ment of Mes­sage Brokers:

                                                                                                                                                                          1. 内置端点代码: 大多数 EAI 套件都包含用于向消息总线发送消息和从消息总线接收消息的所有代码。开发人员不必关心编写任何与传输相关的代码。

                                                                                                                                                                          2. Built-in en­d­point code: Most EAI suites in­cor­por­ate all code to send and re­ceive mes­sages to and from the mes­sage bus. The de­ve­loper does not have to be con­cerned with writ­ing any of the trans­port-re­lated code.

                                                                                                                                                                          3. 可视化设计工具: 这些工具允许开发人员使用可视化组件(例如路由器、决策点和转换器)来组合消息代理的功能。这些工具使消息流在视觉上直观,并且可以将许多组件的编码工作减少到单行代码,例如评估函数或规则。

                                                                                                                                                                          4. Visual design tools: These tools allow the de­ve­loper to com­pose the func­tion­al­ity of a Mes­sage Broker using visual com­pon­ents, such as routers, de­cision points, and trans­formers. These tools make the flow of mes­sages visu­ally in­tu­it­ive and can reduce the coding effort for many of these com­pon­ents to single lines of code, such as an eval­u­ation func­tion or a rule.

                                                                                                                                                                          5. 运行时支持: 大多数 EAI 包还在部署解决方案和监视流经Message Broker 的流量方面提供复杂的运行时支持。

                                                                                                                                                                          6. Runtime sup­port: Most EAI pack­ages also provide soph­ist­ic­ated runtime sup­port in both de­ploy­ing the solu­tion and mon­it­or­ing the traffic flow­ing through the Mes­sage Broker.



                                                                                                                                                                            介绍

                                                                                                                                                                            Introduction

                                                                                                                                                                            正如消息转换器中所述需要由消息传递系统集成的应用程序很少就通用数据格式达成一致。例如,会计系统的客户对象概念与客户关系管理系统不同。最重要的是,一个系统可以将数据保存在关系模型中,而另一个应用程序则使用平面文件或 XML 文档。集成现有应用程序通常意味着我们没有修改应用程序以更轻松地与其他系统配合使用的自由。相反,集成解决方案必须适应并解决不同系统之间的差异。消息翻译模式为数据格式的这种差异提供了通用的解决方案。本章探讨消息转换器的特定变体

                                                                                                                                                                            As de­scribed in the Mes­sage Trans­lator, ap­plic­a­tions that need to be in­teg­rated by a mes­saging system rarely agree on a common data format. For ex­ample, an ac­count­ing system will have a dif­fer­ent notion of a Cus­tomer object than will a cus­tomer re­la­tion­ship man­age­ment system. On top of that, one system may per­sist data in a re­la­tional model, while an­other ap­plic­a­tion uses flat files or XML doc­u­ments. In­teg­rat­ing ex­ist­ing ap­plic­a­tions often means that we do not have the liberty of modi­fy­ing the ap­plic­a­tions to work more easily with other sys­tems. Rather, the in­teg­ra­tion solu­tion has to ac­com­mod­ate and re­solve the dif­fer­ences between the vary­ing sys­tems. The Mes­sage Trans­lator pat­tern offers a gen­eral solu­tion to such dif­fer­ences in data formats. This chapter ex­plores spe­cific vari­ants of the Mes­sage Trans­lator.

                                                                                                                                                                            大多数消息系统对消息头的格式和内容都有特定的要求。我们将消息有效负载数据包装到符合消息传递基础设施要求的信封包装器中。如果消息跨不同的消息基础设施传递,则可以组合多个信封包装器。

                                                                                                                                                                            Most mes­saging sys­tems place spe­cific re­quire­ments on the format and con­tents of a mes­sage header. We wrap mes­sage pay­load data into an En­vel­ope Wrap­per that is com­pli­ant with the re­quire­ments of the mes­saging in­fra­struc­ture. Mul­tiple En­vel­ope Wrap­pers can be com­bined if a mes­sage is passed across dif­fer­ent mes­saging in­fra­struc­tures.

                                                                                                                                                                            如果目标系统需要原始系统无法提供的数据字段,则需要内容丰富器。它能够查找缺失的信息或根据可用数据进行计算。内容过滤器则相反,它会从邮件中删除不需要的数据。声明检查还会从消息中删除数据,但将其存储起来以供以后检索。规范化器将以多种不同格式到达的消息转换为通用格式。

                                                                                                                                                                            A Con­tent En­richer is needed if the target system re­quires data fields that the ori­gin­at­ing system cannot supply. It has the abil­ity to look up miss­ing in­form­a­tion or com­pute it from the avail­able data. The Con­tent Filter does the op­pos­iteit re­moves un­wanted data from a mes­sage. The Claim Check also re­moves data from a mes­sage but stores it for later re­trieval. The Nor­mal­izer trans­lates mes­sages ar­riv­ing in many dif­fer­ent formats into a common format.

                                                                                                                                                                            消除依赖

                                                                                                                                                                            Elim­in­at­ing De­pend­en­cies

                                                                                                                                                                            消息转换是集成中的一个深层次主题。消息通道消息路由器可以消除一个应用程序了解另一个应用程序位置的需要,从而消除应用程序之间的基本依赖关系。

                                                                                                                                                                            Mes­sage trans­form­a­tion is a deep topic in in­teg­ra­tion. Mes­sage Chan­nels and Mes­sage Routers can remove basic de­pend­en­cies between ap­plic­a­tions by elim­in­at­ing the need for one ap­plic­a­tion to be aware of the other's loc­a­tion.

                                                                                                                                                                            一个应用程序可以将消息发送到消息通道,而不必担心哪个应用程序将使用它。然而,消息格式强加了另一组依赖性。如果一个应用程序必须将消息格式化为另一个应用程序的数据格式,那么消息通道形式的解耦在某种程度上一种幻想。对接收应用程序的任何更改或从一个接收应用程序切换到另一接收应用程序仍然需要对发送应用程序进行更改。消息翻译器有助于消除这种依赖性。

                                                                                                                                                                            One ap­plic­a­tion can send a mes­sage to a Mes­sage Chan­nel and not worry about what ap­plic­a­tion will con­sume it. How­ever, mes­sage formats impose an­other set of de­pend­en­cies. If one ap­plic­a­tion has to format mes­sages in an­other ap­plic­a­tion's data format, the de­coup­ling in the form of the Mes­sage Chan­nel is some­what of an il­lu­sion. Any change to the re­ceiv­ing ap­plic­a­tion or the switch from one re­ceiv­ing ap­plic­a­tion to an­other still re­quires a change to the send­ing ap­plic­a­tion. Mes­sage Trans­lat­ors help remove this de­pend­ency.

                                                                                                                                                                            元数据管理

                                                                                                                                                                            Metadata Man­age­ment

                                                                                                                                                                            将消息从一种格式转换为另一种格式需要我们处理描述实际数据格式的元数据。虽然从一个应用程序发送到另一个应用程序的消息可能会告诉我们 ID 为 123 的客户从加利福尼亚州旧金山搬到北卡罗来纳州罗利,但关联的元数据可能会告诉我们此地址更改消息使用数字客户 ID 字段并存储客户的名字和姓氏位于两个文本字段中,每个字段最多 40 个字符。

                                                                                                                                                                            Trans­form­ing mes­sages from one format into an­other re­quires us to deal with metadatadata that de­scribes the format of actual data. While a mes­sage from one ap­plic­a­tion to an­other may tell us that the cus­tomer with the ID 123 moved from San Fran­cisco, Cali­for­nia, to Raleigh, North Car­o­lina, the as­so­ci­ated metadata may tell us that this Ad­dress Change mes­sage uses a nu­meric cus­tomer ID field and stores the first and last names of the cus­tomer in two text fields of up to 40 char­ac­ters each.

                                                                                                                                                                            元数据在集成中扮演着如此重要的角色,我们可以将大多数集成解决方案视为两个并行系统之间的相互作用。一个处理实际的消息数据,另一个处理元数据。许多用于设计消息数据流的模式也可用于管理元数据流。例如,通道适配器不仅可以将消息移入和移出系统,还可以从外部应用程序提取元数据并将其加载到中央元数据存储库中。使用此存储库,集成开发人员可以定义应用程序元数据和规范数据模型之间的转换。

                                                                                                                                                                            Metadata plays such an im­port­ant role in in­teg­ra­tion that we can view most in­teg­ra­tion solu­tions as in­ter­play between to two par­al­lel sys­tems. One deals with actual mes­sage data, the other with metadata. Many of the pat­terns used to design the flow of mes­sage data can also be used to manage the flow of metadata. For ex­ample, a Chan­nel Ad­apter not only can move mes­sages in and out of a system, but can also ex­tract metadata from ex­ternal ap­plic­a­tions and load it into a cent­ral metadata re­pos­it­ory. Using this re­pos­it­ory, the in­teg­ra­tion de­ve­lopers can define trans­form­a­tions between the ap­plic­a­tion metadata and the Ca­non­ical Data Model.

                                                                                                                                                                            元数据集成

                                                                                                                                                                            Metadata In­teg­ra­tion

                                                                                                                                                                            图形/08inf01.gif

                                                                                                                                                                            例如,上图描述了两个需要交换客户信息的应用程序之间的集成。每个系统对客户数据的定义略有不同。应用程序 A 将名字和姓氏存储在两个单独的字段中,而应用程序 B 将它们存储在一个字段中。同样,应用程序 A 存储客户的邮政编码而不是州,而应用程序 B 只存储州缩写。从应用程序 A 流向应用程序 B 的消息必须经过转换,以便应用程序 B 可以接收所需格式的数据。如果通道适配器还可以提取元数据(例如,描述消息格式的数据)。然后可以将该元数据加载到存储库中,从而大大简化消息转换器的配置和验证。 元数据可以以多种格式存储。用于 XML 消息的常见格式是 XSDeXtensible Schema Definition。其他 EAI 工具实现专有的元数据格式,但允许管理员将元数据导入和导出为不同的格式。

                                                                                                                                                                            For ex­ample, the pre­vi­ous figure de­picts the in­teg­ra­tion between two ap­plic­a­tions that need to ex­change cus­tomer in­form­a­tion. Each system has a slightly dif­fer­ent defin­i­tion of cus­tomer data. Ap­plic­a­tion A stores first and last names in two sep­ar­ate fields, whereas Ap­plic­a­tion B stores them in one field. Like­wise, Ap­plic­a­tion A stores the cus­tomer's ZIP code and not the state, while Ap­plic­a­tion B stores only the state ab­bre­vi­ation. Mes­sages flow­ing from Ap­plic­a­tion A to Ap­plic­a­tion B have to un­dergo a trans­form­a­tion so that Ap­plic­a­tion B can re­ceive data in the re­quired format. Cre­at­ing the trans­form­a­tion is much sim­pli­fied if the Chan­nel Ad­apters can also ex­tract metadata (e.g., data de­scrib­ing the mes­sage format). This metadata can then be loaded into a re­pos­it­ory, greatly sim­pli­fy­ing the con­fig­ur­a­tion and val­id­a­tion of the Mes­sage Trans­lator. The metadata can be stored in a vari­ety of formats. A common format used for XML mes­sages is XS­DeX­tens­ible Schema Defin­i­tion. Other EAI tools im­ple­ment pro­pri­et­ary metadata formats but allow ad­min­is­trat­ors to import and export metadata into dif­fer­ent formats.

                                                                                                                                                                            消息传递之外的数据转换

                                                                                                                                                                            Data Trans­form­a­tion Out­side of Mes­saging

                                                                                                                                                                            这些转换模式中包含的许多原则也适用于基于消息的集成之外。例如,文件传输必须执行系统之间的转换功能。同样,远程过程调用即使应用程序的内部格式不同,也必须以要调用的服务指定的数据格式发出请求。这通常需要调用应用程序执行数据转换。一些最复杂的转换引擎已集成到 ETL(提取、转换、加载)工具中,例如 Informatica 或 DataMirror。这些工具通常会立即转换大量数据,而不是转换单个消息。

                                                                                                                                                                            Many of the prin­ciples in­cor­por­ated in these trans­form­a­tion pat­terns are ap­plic­able out­side of mes­sage-based in­teg­ra­tion. For ex­ample, File Trans­fer has to per­form trans­form­a­tion func­tions between sys­tems. Like­wise, Remote Pro­ced­ure In­voc­a­tion has to make re­quests in the data format spe­cified by the ser­vice that is to be called even if the ap­plic­a­tion's in­ternal format is dif­fer­ent. This typ­ic­ally re­quires the call­ing ap­plic­a­tion to per­form a trans­form­a­tion of data. Some of the most soph­ist­ic­ated trans­form­a­tion en­gines are in­cor­por­ated into ETL (ex­tract, trans­form, load) tools, such as In­form­at­ica or DataMir­ror. These tools typ­ic­ally trans­form a large set of data at once in­stead of trans­form­ing in­di­vidual mes­sages.

                                                                                                                                                                            本章重点介绍基本消息转换器模式的变体。它没有详细介绍实体之间的结构转换(例如,如果一个模型支持客户和地址之间的多对多关系,但另一个模型包含客户记录上的地址字段,则如何在两个数据模型之间进行转换)。关于呈现数据和关系的主题最古老且仍然最相关的书籍之一是[ Kent ]。

                                                                                                                                                                            This chapter fo­cuses on vari­ations of the basic Mes­sage Trans­lator pat­tern. It does not go into the de­tails of struc­tural trans­form­a­tions between en­tit­ies (e.g., how to trans­form between two data models if one model sup­ports many-to-many re­la­tion­ships between cus­tomer and ad­dress but the other model in­cludes ad­dress fields on the cus­tomer record). One of the oldest and still most rel­ev­ant books on the topic of present­ing data and re­la­tion­ships is [Kent].

                                                                                                                                                                              信封包装纸

                                                                                                                                                                              Envelope Wrapper

                                                                                                                                                                              图形/envelopewrapper_icon.gif

                                                                                                                                                                              大多数消息传递系统将消息数据分为标题和正文(请参阅Message )。标头包含消息传递基础结构用于管理消息流的字段。然而,参与集成解决方案的大多数端点系统通常不知道这些额外的数据元素。在某些情况下,系统甚至可能认为这些字段是错误的,因为它们与应用程序使用的消息格式不匹配。另一方面,在应用程序之间路由消息的消息传递组件可能需要标头字段,并且如果消息不包含正确的标头字段,则会认为消息无效。

                                                                                                                                                                              Most mes­saging sys­tems divide the mes­sage data into a header and a body (see Mes­sage). The header con­tains fields that are used by the mes­saging in­fra­struc­ture to manage the flow of mes­sages. How­ever, most en­d­point sys­tems that par­ti­cip­ate in the in­teg­ra­tion solu­tion gen­er­ally are not aware of these extra data ele­ments. In some cases, sys­tems may even con­sider these fields as er­ro­neous be­cause they do not match the mes­sage format used by the ap­plic­a­tion. On the other hand, the mes­saging com­pon­ents that route the mes­sages between the ap­plic­a­tions may re­quire the header fields and would con­sider a mes­sage in­valid if it did not con­tain the proper header fields.

                                                                                                                                                                              现有系统如何参与对消息格式提出特定要求(例如消息头字段或加密)的消息交换?

                                                                                                                                                                              How can ex­ist­ing sys­tems par­ti­cip­ate in a mes­saging ex­change that places spe­cific re­quire­ments, such as mes­sage header fields or en­cryp­tion, on the mes­sage format?



                                                                                                                                                                              例如,假设消息传递系统正在使用专有的安全方案。有效的消息必须包含安全凭证,以便其他消息传递组件接受该消息进行处理。这种方案对于防止未经授权的用户将消息输入系统很有用。此外,消息内容可以被加密,以防止未经授权的监听者窃听——这是发布-订阅机制的一个特别重要的问题。然而,通过消息传递系统集成的现有应用程序很可能不知道用户身份或消息加密的概念。因此,“原始”消息需要转换为符合消息传递系统规则的消息。

                                                                                                                                                                              For ex­ample, assume the mes­saging system is using a pro­pri­et­ary se­cur­ity scheme. A valid mes­sage would have to con­tain se­cur­ity cre­den­tials for the mes­sage to be ac­cep­ted for pro­cess­ing by other mes­saging com­pon­ents. Such a scheme is useful to pre­vent un­au­thor­ized users from feed­ing mes­sages into the system. Ad­di­tion­ally, the mes­sage con­tent may be en­cryp­ted to pre­vent eaves­drop­ping by un­au­thor­ized listen­ersa par­tic­u­larly im­port­ant issue with pub­lish-sub­scribe mech­an­isms. How­ever, ex­ist­ing ap­plic­a­tions that are being in­teg­rated via the mes­saging sys­tems are most likely not aware of the con­cepts of user iden­tity or mes­sage en­cryp­tion. As a result, "raw" mes­sages need to be trans­lated into mes­sages that comply with the rules of the mes­saging system.

                                                                                                                                                                              一些大型企业使用多个消息传递基础设施。因此,消息可能必须使用消息传递桥在消息传递系统之间路由。每个消息系统可能对消息正文和消息头的格式有不同的要求。这种情况是我们可以通过查看现有的基于 TCP/IP 的网络协议来学习的另一种情况。在许多情况下,与另一个系统的连接仅限于特定协议,例如 Telnet 或 Secure Shell ( ssh)。为了使用其他协议(例如 FTP)进行通信,必须将该协议格式封装到符合支持协议的数据包中。在另一端,可以提取数据包有效负载。这个过程称为隧道效应

                                                                                                                                                                              Some large en­ter­prises use more than one mes­saging in­fra­struc­ture. Thus, a mes­sage may have to be routed across mes­saging sys­tems using a Mes­saging Bridge. Each mes­saging system is likely to have dif­fer­ent re­quire­ments for the format of the mes­sage body as well as the header. This scen­ario is an­other case where we can learn by look­ing at ex­ist­ing TCP/IP-based net­work pro­to­cols. In many cases, con­nectiv­ity to an­other system is re­stric­ted to a spe­cific pro­tocol, such as Telnet or Secure Shell (ssh). In order to enable com­mu­nic­a­tion using an­other pro­tocol (for ex­ample, FTP), that pro­tocol format has to be en­cap­su­lated into pack­ets that con­form to the sup­por­ted pro­tocol. At the other end, the packet pay­load can be ex­trac­ted. This pro­cess is called tun­nel­ing.

                                                                                                                                                                              当一种消息格式封装在另一种消息格式中时,系统可能无法访问数据有效负载内的信息。大多数消息传递系统允许组件(例如,消息路由器)仅访问属于已定义消息头的一部分的数据字段。如果一条消息被打包到另一条消息内的数据字段中,则组件可能无法使用原始消息中的字段来执行路由或转换功能。因此,一些数据字段可能必须从原始消息提升到新消息格式的消息头中。

                                                                                                                                                                              When one mes­sage format is en­cap­su­lated inside an­other, the system may lose access to the in­form­a­tion inside the data pay­load. Most mes­saging sys­tems allow com­pon­ents (for ex­ample, a Mes­sage Router) to access only data fields that are part of the defined mes­sage header. If one mes­sage is pack­aged into a data field inside an­other mes­sage, the com­pon­ent may not be able to use the fields from the ori­ginal mes­sage to per­form rout­ing or trans­form­a­tion func­tions. There­fore, some data fields may have to be el­ev­ated from the ori­ginal mes­sage into the mes­sage header of the new mes­sage format.

                                                                                                                                                                              使用信封包装器将应用程序数据包装在符合消息传递基础结构的信封内。当消息到达目的地时将其拆开。

                                                                                                                                                                              Use an En­vel­ope Wrap­per to wrap ap­plic­a­tion data inside an en­vel­ope that is com­pli­ant with the mes­saging in­fra­struc­ture. Unwrap the mes­sage when it ar­rives at the des­tin­a­tion.

                                                                                                                                                                              图形/08inf02.gif



                                                                                                                                                                              包装和解开消息的过程包括五个步骤:

                                                                                                                                                                              The pro­cess of wrap­ping and un­wrap­ping a mes­sage con­sists of five steps:

                                                                                                                                                                              1. 消息源以原始格式发布消息。这种格式通常由应用程序的性质决定,并且不符合消息传递基础设施的要求。

                                                                                                                                                                              2. The mes­sage source pub­lishes a mes­sage in a raw format. This format is typ­ic­ally de­term­ined by the nature of the ap­plic­a­tion and does not comply with the re­quire­ments of the mes­saging in­fra­struc­ture.

                                                                                                                                                                              3. 包装器获取原始消息并将其转换为符合消息传递系统的消息格式。这可能包括添加消息标头字段、加密消息、添加安全凭证等。

                                                                                                                                                                              4. The wrap­per takes the raw mes­sage and trans­forms it into a mes­sage format that com­plies with the mes­saging system. This may in­clude adding mes­sage header fields, en­crypt­ing the mes­sage, adding se­cur­ity cre­den­tials, and so on.

                                                                                                                                                                              5. 消息传递系统传输兼容的消息。

                                                                                                                                                                              6. The mes­saging system trans­ports the com­pli­ant mes­sages.

                                                                                                                                                                              7. 结果消息被传送到解包器。解包器会反转包装器所做的任何修改。这可能包括删除标头字段、解密消息或验证安全凭证。

                                                                                                                                                                              8. A res­ult­ing mes­sage is de­livered to the un­wrap­per. The un­wrap­per re­verses any modi­fic­a­tions the wrap­per made. This may in­clude re­mov­ing header fields, de­crypt­ing the mes­sage, or veri­fy­ing se­cur­ity cre­den­tials.

                                                                                                                                                                              9. 消息接收者收到“明文”消息。

                                                                                                                                                                              10. The mes­sage re­cip­i­ent re­ceives a "clear text" mes­sage.

                                                                                                                                                                              信封通常包装消息头和消息正文或有效负载。我们可以将标头视为邮政信封外部的信息:消息传递系统使用它来路由和跟踪消息。信封的内容是有效负载或正文,消息传递基础设施在到达目的地之前并不关心它(在某些限制内)。

                                                                                                                                                                              An en­vel­ope typ­ic­ally wraps both the mes­sage header and the mes­sage body, or pay­load. We can think of the header as being the in­form­a­tion on the out­side of a postal en­vel­ope: It is used by the mes­saging system to route and track the mes­sage. The con­tents of the en­vel­ope is the pay­load or bo­dythe mes­saging in­fra­struc­ture does not care much about it (within cer­tain lim­it­a­tions) until it ar­rives at the des­tin­a­tion.

                                                                                                                                                                              包装器通常会向原始消息添加信息。例如,在通过邮政系统发送内部消息之前,必须查找邮政编码。从这个意义上说,包装器包含了内容丰富器的某些方面。然而,包装器并没有丰富实际的信息内容,而是添加了消息路由、跟踪和处理所需的信息。该信息可以动态创建(例如,创建唯一的消息 ID 或添加时间戳),可以从基础设施中提取(例如,检索安全上下文),或者数据可以包含在原始消息中主体,然后由包装器分割成消息头(例如,原始消息中包含的关键字段)。最后一个选项有时称为升级,因为特定字段从隐藏在正文中“升级”为在标头中显着可见。

                                                                                                                                                                              It is typ­ical for wrap­pers to add in­form­a­tion to the raw mes­sage. For ex­ample, before an in­ternal mes­sage can be sent through the postal system, a ZIP code has to be looked up. In that sense, wrap­pers in­cor­por­ate some as­pects of a Con­tent En­richer. How­ever, wrap­pers do not enrich the actual in­form­a­tion con­tent, but add in­form­a­tion that is ne­ces­sary for the rout­ing, track­ing, and hand­ling of mes­sages. This in­form­a­tion can be cre­ated on the fly (e.g., cre­at­ing a unique mes­sage ID or adding a time stamp), it can be ex­trac­ted from the in­fra­struc­ture (e.g., re­trieval of a se­cur­ity con­text), or the data may be con­tained in the ori­ginal mes­sage body and then split by the wrap­per into the mes­sage header (e.g., a key field con­tained in the raw mes­sage). This last option is some­times re­ferred to as pro­mo­tion be­cause a spe­cific field is "pro­moted" from being hidden inside the body to being prom­in­ently vis­ible in the header.

                                                                                                                                                                              通常,多个包装器和拆包器被链接起来(请参阅以下邮政系统示例),利用分层协议模型。这会导致消息的有效负载包含一个新信封,而新信封又包装了标头和有效负载部分(见图)。

                                                                                                                                                                              Fre­quently, mul­tiple wrap­pers and un­wrap­pers are chained (see the fol­low­ing postal system ex­ample), taking ad­vant­age of the layered pro­tocol model. This res­ults in a situ­ation where the pay­load of a mes­sage con­tains a new en­vel­ope, which in turn wraps a header and a pay­load sec­tion (see figure).

                                                                                                                                                                              包装器链创建了分层信封结构

                                                                                                                                                                              A Chain of Wrap­pers Cre­ates a Hier­arch­ical En­vel­ope Struc­ture

                                                                                                                                                                              图形/08inf03.gif

                                                                                                                                                                              示例: SOAP 消息格式

                                                                                                                                                                              Ex­ample: SOAP Mes­sage Format

                                                                                                                                                                              基本 SOAP 消息格式 [ SOAP 1.1] 比较简单。它指定包含消息头和消息正文的信封。以下示例说明正文如何包含另一个信封,而另一个信封又包含另一个标头和正文。组合消息被发送到中介,该中介解开外部消息并转发内部消息。在跨越信任边界时,这种中介链是非常常见的。我们可以对所有消息进行编码并将它们包装在另一条消息中,以便没有中间人可以看到消息内容或标头(例如,消息的地址可能是机密的)。然后,接收者解开消息,解码有效负载,并将未编码的消息通过可信环境传递。

                                                                                                                                                                              The basic SOAP mes­sage format [SOAP 1.1] is re­l­at­ively simple. It spe­cifies an en­vel­ope that con­tains a mes­sage header and a mes­sage body. The fol­low­ing ex­ample il­lus­trates how the body can con­tain an­other en­vel­ope, which in turn con­tains an­other header and body. The com­bined mes­sage is sent to an in­ter­me­di­ary that un­wraps the out­side mes­sage and for­wards the inside mes­sage. This chain­ing of in­ter­me­di­ar­ies is very common when cross­ing trust bound­ar­ies. We may encode all our mes­sages and wrap them inside an­other mes­sage so that no in­ter­me­di­ary can see the mes­sage con­tent or header (e.g., the ad­dress of a mes­sage may be con­fid­en­tial). The re­cip­i­ent then un­wraps the mes­sage, de­codes the pay­load, and passes the un­en­coded mes­sage through the trus­ted en­vir­on­ment.

                                                                                                                                                                              
                                                                                                                                                                              <env:信封 xmlns:env="http://www.w3.org/2001/06/soap-envelope">
                                                                                                                                                                                  <env:Header env:actor="http://example.org/xmlsec/Bob">
                                                                                                                                                                                      <n:forward xmlns:n="http://example.org/xmlsec/forwarding">
                                                                                                                                                                                          <n:窗口>120</n:窗口>
                                                                                                                                                                                      </n:转发>
                                                                                                                                                                                  </env:标题>
                                                                                                                                                                                  <环境:正文>
                                                                                                                                                                                      <env:信封 xmlns:env="http://www.w3.org/2001/06
                                                                                                                                                                              图形/ccc.gif/肥皂信封">
                                                                                                                                                                                          <env:Header env:actor="http://example.org/xmlsec/Alice"/>
                                                                                                                                                                                          <环境:正文>
                                                                                                                                                                                              <秘密 xmlns="http://example.org/xmlsec/message">
                                                                                                                                                                                    黑松鼠黎明时分</secret>
                                                                                                                                                                                          </env:正文>
                                                                                                                                                                                      </env:信封>
                                                                                                                                                                                  </env:正文>
                                                                                                                                                                              </env:信封>
                                                                                                                                                                              
                                                                                                                                                                              
                                                                                                                                                                              <env:En­vel­ope xmlns:env="http://www.w3.org/2001/06/soap-en­vel­ope">
                                                                                                                                                                                  <env:Header env:actor="http://ex­ample.org/xmlsec/Bob">
                                                                                                                                                                                      <n:for­ward xmlns:n="http://ex­ample.org/xmlsec/for­ward­ing">
                                                                                                                                                                                          <n:window>120</n:window>
                                                                                                                                                                                      </n:for­ward>
                                                                                                                                                                                  </env:Header>
                                                                                                                                                                                  <env:Body>
                                                                                                                                                                                      <env:En­vel­ope xmlns:env="http://www.w3.org/2001/06
                                                                                                                                                                              /soap-en­vel­ope">
                                                                                                                                                                                          <env:Header env:actor="http://ex­ample.org/xmlsec/Alice"/>
                                                                                                                                                                                          <env:Body>
                                                                                                                                                                                              <secret xmlns="http://ex­ample.org/xmlsec/mes­sage">
                                                                                                                                                                                    The black squir­rel rises at dawn</secret>
                                                                                                                                                                                          </env:Body>
                                                                                                                                                                                      </env:En­vel­ope>
                                                                                                                                                                                  </env:Body>
                                                                                                                                                                              </env:En­vel­ope>
                                                                                                                                                                              



                                                                                                                                                                              示例: TCP/IP

                                                                                                                                                                              Ex­ample: TCP/IP

                                                                                                                                                                              虽然我们通常使用TCP/IP作为一个术语,但它实际上包含两个协议。IP 协议提供基本的寻址和路由服务,而 TCP 则提供位于 IP 之上的可靠的、面向连接的协议。遵循 OSI 层模型,TCP 是传输协议,而 IP 是网络协议。通常,TCP/IP 数据通过以太网传输,以太网实现了链路层。

                                                                                                                                                                              While we com­monly use TCP/IP as one term, it ac­tu­ally com­prises two pro­to­cols. The IP pro­tocol provides basic ad­dress­ing and rout­ing ser­vices, while TCP provides a re­li­able, con­nec­tion-ori­ented pro­tocol that is layered on top of IP. Fol­low­ing the OSI layer model, TCP is a trans­port pro­tocol, while IP is a net­work pro­tocol. Typ­ic­ally, TCP/IP data is trans­por­ted over an Eth­er­net net­work, which im­ple­ments the link layer.

                                                                                                                                                                              因此,应用程序数据首先被包装到 TCP 信封中,然后被包装到 IP 信封中,最后被包装到以太网信封中。由于网络是面向流的,因此信封可以由标头和尾部组成,标记数据流的开始和结束。下页的图说明了通过以太网传输的应用程序数据的结构。

                                                                                                                                                                              As a result, ap­plic­a­tion data is wrapped into a TCP en­vel­ope first, which is then wrapped into an IP en­vel­ope, which is then wrapped into an Eth­er­net en­vel­ope. Since net­works are stream-ori­ented, an en­vel­ope can con­sist of both a header and a trailer, mark­ing the be­gin­ning and the end of the data stream. The figure on the fol­low­ing page il­lus­trates the struc­ture of ap­plic­a­tion data trav­el­ing over the Eth­er­net.

                                                                                                                                                                              应用程序数据被包装在多个信封内以通过网络传输

                                                                                                                                                                              Ap­plic­a­tion Data Is Wrapped Inside Mul­tiple En­vel­opes to be Trans­por­ted over the Net­work

                                                                                                                                                                              图形/08inf04.gif

                                                                                                                                                                              您可以看到应用程序数据连续包装到多个信封中:TCP(传输信封)、IP(网络信封)和以太网(链路信封)。TCP 和 IP 信封仅包含报头,而以太网信封则添加报头和报尾。如果您对 TCP/IP 的更多细节感兴趣,[ Stevens ] 一定能满足您的求知欲。

                                                                                                                                                                              You can see the suc­cess­ive wrap­ping of the ap­plic­a­tion data into mul­tiple en­vel­opes: TCP (trans­port en­vel­ope), IP (net­work en­vel­ope), and Eth­er­net (link en­vel­ope). The TCP and the IP en­vel­opes con­sist of only a header, while the Eth­er­net en­vel­ope adds both a header and a trailer. If you are in­ter­ested in more de­tails about TCP/IP, [Stevens] is guar­an­teed to quench your thirst for know­ledge.



                                                                                                                                                                              示例: 邮政系统

                                                                                                                                                                              Ex­ample: The Postal System

                                                                                                                                                                              信封包装纸模式可以与邮政系统进行比较(参见下页的图)。假设一名员工为同事创建了一份内部备忘录。任何纸张都可以是此消息有效负载的可接受格式。为了交付备忘录,必须将其“包装”到公司内部的信封中,其中包含收件人的姓名和部门代码。如果收件人在单独的机构工作,该公司内部消息将被装入一个大信封并通过美国邮政服务邮寄。为了使新邮件符合 USPS 要求,需要使用带有邮政编码和邮资的新信封。美国邮政局可能决定通过航空运输该信封。为此,它将特定区域的所有信封放入邮袋中,其地址是一个条形码,其中包含目的地机场的三个字母的机场代码。一旦邮袋到达目的地机场,包装顺序就会相反,直到同事收到原始备忘录。这个例子说明了这个术语隧道:邮政邮件可以通过空运进行“隧道”传输,就像 UDP 多播数据包可以通过 TCP/IP 连接进行隧道传输一样,以便到达不同的 WAN 网段。

                                                                                                                                                                              The En­vel­ope Wrap­per pat­tern can be com­pared to the postal system (see figure on the next page). Let's assume an em­ployee cre­ates an in­ternal memo to a fellow em­ployee. Any sheet of paper will be an ac­cept­able format for this mes­sage pay­load. In order for the memo to be de­livered, it has to be "wrapped" into an intra-com­pany en­vel­ope that con­tains the re­cip­i­ent's name and de­part­ment code. If the re­cip­i­ent works in a sep­ar­ate fa­cil­ity, this intra-com­pany mes­sage will be stuffed into a large en­vel­ope and mailed via the U.S. Postal Ser­vice. In order to make the new mes­sage comply with the USPS re­quire­ments, it needs to fea­ture a new en­vel­ope with ZIP code and post­age. The USPS may decide to trans­port this en­vel­ope via air. To do so, it stuffs all en­vel­opes for a spe­cific region into a mail­bag, which is ad­dressed with a bar code fea­tur­ing the three-letter air­port code for the des­tin­a­tion air­port. Once the mail­bag ar­rives at the des­tin­a­tion air­port, the wrap­ping se­quence is re­versed until the ori­ginal memo is re­ceived by the coworker. This ex­ample il­lus­trates the term tun­nel­ing: Postal mail may be "tunneled" through air freight just as UDP mulit­cast pack­ets may be tunneled over a TCP/IP con­nec­tion in order to reach a dif­fer­ent WAN seg­ment.

                                                                                                                                                                              图形/08inf05.gif

                                                                                                                                                                              邮政系统示例说明了使用管道和过滤器架构链接包装器和拆包器的常见做法。消息可能由多个步骤进行包装,并且需要通过一系列对称的解包步骤进行解包。正如管道和过滤器中所述,保持各个步骤彼此独立使消息传递基础结构能够灵活地添加或删除包装和展开步骤。例如,可能不再需要加密,因为所有流量都通过 VPN 而不是公共互联网进行路由。

                                                                                                                                                                              The postal system ex­ample il­lus­trates the common prac­tice of chain­ing wrap­pers and un­wrap­pers using the Pipes and Fil­ters ar­chi­tec­ture. Mes­sages may be wrapped by more than one step and need to be un­wrapped by a sym­met­ric se­quence of un­wrap­ping steps. As laid out in Pipes and Fil­ters, keep­ing the in­di­vidual steps in­de­pend­ent from each other gives the mes­saging in­fra­struc­ture the flex­ib­il­ity to add or remove wrap­ping and un­wrap­ping steps. For ex­ample, en­cryp­tion may no longer be re­quired be­cause all traffic is routed across a VPN as op­posed to the public In­ter­net.



                                                                                                                                                                                内容丰富器

                                                                                                                                                                                Content Enricher

                                                                                                                                                                                图形/contentenricher_icon.gif

                                                                                                                                                                                当从一个系统向另一个系统发送消息时,目标系统通常需要比源系统可以提供的信息更多的信息。例如,传入的地址消息可能只包含邮政编码,因为设计者认为存储冗余的城市和州信息是多余的。另一个系统可能想要指定城市和州以及邮政编码字段。然而另一个系统实际上可能不使用州缩写,而是拼出州名称,因为它使用自由格式地址来支持国际地址。同样,一个系统可能会向我们提供客户 ID,但接收系统实际上需要客户姓名和地址。在另一种情况下,订单管理系统发送的订单消息可能只包含订单号,但我们需要找到与该订单关联的客户 ID,以便将其传递给客户管理系统。场景很丰富。

                                                                                                                                                                                When send­ing mes­sages from one system to an­other, it is common for the target system to re­quire more in­form­a­tion than the source system can provide. For ex­ample, in­com­ing Ad­dress mes­sages may just con­tain the ZIP code be­cause the de­sign­ers felt that stor­ing a re­dund­ant city and state in­form­a­tion would be su­per­flu­ous. Likely, an­other system will want to spe­cify city and state as well as a ZIP code field. Yet an­other system may not ac­tu­ally use state ab­bre­vi­ations, but spell the state name out be­cause it uses free­form ad­dresses in order to sup­port in­ter­na­tional ad­dresses. Like­wise, one system may provide us with a cus­tomer ID, but the re­ceiv­ing system ac­tu­ally re­quires the cus­tomer name and ad­dress. In yet an­other situ­ation, an Order mes­sage sent by the order man­age­ment system may just con­tain an order number, but we need to find the cus­tomer ID as­so­ci­ated with that order so we can pass it to the cus­tomer man­age­ment system. The scen­arios are plen­ti­ful.

                                                                                                                                                                                如果消息发起者没有提供所有必需的数据项,我们如何与另一个系统通信?

                                                                                                                                                                                How do we com­mu­nic­ate with an­other system if the mes­sage ori­gin­ator does not have all the re­quired data items avail­able?



                                                                                                                                                                                此问题是消息转换器的特例因此一些相同的注意事项也适用。然而,这个问题与消息翻译器中描述的基本示例略有不同。 消息转换器的描述假设接收应用程序所需的数据已包含在传入消息中,尽管格式错误。在这个新案例中,这不是一个简单的重新排列字段的问题;而是一个简单的问题。我们实际上需要向消息中注入附加信息。

                                                                                                                                                                                This prob­lem is a spe­cial case of the Mes­sage Trans­lator, so some of the same con­sid­er­a­tions apply. How­ever, this prob­lem is slightly dif­fer­ent from the basic ex­amples de­scribed in the Mes­sage Trans­lator. The de­scrip­tion of the Mes­sage Trans­lator as­sumed that the data needed by the re­ceiv­ing ap­plic­a­tion is already con­tained in the in­com­ing mes­sage, albeit in the wrong format. In this new case, it is not a simple matter of re­arran­ging fields; we ac­tu­ally need to inject ad­di­tional in­form­a­tion to the mes­sage.

                                                                                                                                                                                会计系统需要的信息多于调度系统所能提供的信息

                                                                                                                                                                                The Ac­count­ing System Re­quires More In­form­a­tion Than the Schedul­ing System Can De­liver

                                                                                                                                                                                图形/08inf06.gif

                                                                                                                                                                                让我们考虑以下示例(见图)。医院调度系统发布一条消息,宣布患者已完成就诊。该消息包含患者的名字、患者 ID 以及就诊日期。为了让会计系统记录这次就诊并通知保险公司,它需要患者的完整姓名、保险公司和患者的社会安全号码。然而,调度系统并不存储这些信息;它包含在客户服务系统中。我们有什么选择?

                                                                                                                                                                                Let's con­sider the fol­low­ing ex­ample (see figure). A hos­pital schedul­ing system pub­lishes a mes­sage an­noun­cing that the pa­tient has com­pleted a doctor's visit. The mes­sage con­tains the pa­tient's first name, his or her pa­tient ID, and the date of the visit. In order for the ac­count­ing system to log this visit and inform the in­sur­ance com­pany, it re­quires the full pa­tient name, the in­sur­ance car­rier, and the pa­tient's social se­cur­ity number. How­ever, the schedul­ing system does not store this in­form­a­tion; it is con­tained in the cus­tomer care system. What are our op­tions?

                                                                                                                                                                                浓缩器问题的可能解决方案

                                                                                                                                                                                Pos­sible Solu­tions for the En­richer Prob­lem

                                                                                                                                                                                图形/08inf07.gif

                                                                                                                                                                                选项A: 我们可以修改调度系统,以便它可以存储附加信息。当客户服务系统中的客户信息发生变化时(例如,由于患者更换保险公司),需要将这些变化复制到调度系统。调度系统现在可以发送包含所有必需信息的消息。不幸的是,这种方法有两个明显的缺点。首先,需要对调度系统的内部结构进行修改。在大多数情况下,调度系统是打包的应用程序,并且可能不允许这种类型的修改。其次,即使调度系统是可定制的,我们也需要考虑到我们正在根据另一个系统的特定需求对系统进行更改。例如,如果我们还想向患者发送一封确认就诊的信件,我们将不得不再次更改日程安排系统以适应客户的邮寄地址。如果我们将调度系统与使用 Doctor Visit 消息的应用程序的具体细节分离,那么集成解决方案的可维护性将会更高。

                                                                                                                                                                                选项 B: 调度系统可以在发送医生就诊消息之前向客户服务系统请求 SSN 和运营商数据,而不是将客户信息存储在调度系统内。这就解决了第一个问题,我们不再需要修改调度系统的存储。然而,第二个问题仍然存在:调度系统需要知道需要SSN和运营商信息,以便通知计费系统。因此,该消息的语义更类似于“Notify Insurance”而不是“Doctor Visit”。在松散耦合的系统中,我们不希望一个系统指示下一个系统做什么。我们改为发送事件消息并让其他系统决定要做什么。此外,该解决方案将调度系统与客户服务系统更紧密地结合在一起,因为调度系统现在需要知道从哪里获取丢失的数据。这将调度系统与会计系统和客户服务系统联系起来。这种类型的耦合是不受欢迎的,因为它会导致脆弱的集成解决方案。

                                                                                                                                                                                选项C: 如果我们首先将消息发送到客户服务系统而不是会计系统,我们可以避免其中一些依赖性。然后,客户服务系统可以获取所有必需的信息,并将包含所有必需数据的消息发送到会计系统。这很好地将调度系统与后续消息流解耦。但现在我们在客服系统内部实现了病人看病后保险公司收到账单的业务规则。这就需要我们修改客户服务系统内部的逻辑。如果客户服务系统是打包的应用程序,则这种修改可能很困难或不可能。即使我们可以做出这样的修改,我们现在让客户服务系统间接负责发送计费消息。如果会计系统所需的所有数据项在客户服务系统中都可用,这可能不是问题。但是,如果必须从其他系统检索某些字段,我们就会遇到与开始时类似的情况。

                                                                                                                                                                                选项 D(未显示): 我们还可以修改会计系统,只需要客户 ID,并从客户服务系统检索 SSN 和运营商信息。这种方法有两个缺点。首先,我们现在将会计系统与客户服务系统结合起来。其次,这个选项再次假设我们拥有对会计系统的控制权。在大多数情况下,会计系统是一个打包的应用程序,定制选项有限。

                                                                                                                                                                                Option A: We could modify the schedul­ing system so it can store the ad­di­tional in­form­a­tion. When the cus­tomer's in­form­a­tion changes in the cus­tomer care system (e.g., be­cause the pa­tient switches in­sur­ance car­ri­ers), the changes need to be rep­lic­ated to the schedul­ing system. The schedul­ing system can now send a mes­sage that in­cludes all re­quired in­form­a­tion. Un­for­tu­nately, this ap­proach has two sig­ni­fic­ant draw­backs. First, it re­quires a modi­fic­a­tion to the schedul­ing system's in­ternal struc­ture. In most cases, the schedul­ing system is a pack­aged ap­plic­a­tion and may not allow this type of modi­fic­a­tion. Second, even if the schedul­ing system is cus­tom­iz­able, we need to con­sider that we are making a change to the system based on the spe­cific needs of an­other system. For ex­ample, if we also want to send a letter to the pa­tient con­firm­ing the visit, we would have to change the schedul­ing system again to ac­com­mod­ate the cus­tomer's mail­ing ad­dress. The in­teg­ra­tion solu­tion would be much more main­tain­able if we de­coupled the schedul­ing system from the spe­cif­ics of the ap­plic­a­tions that con­sume the Doctor Visit mes­sage.

                                                                                                                                                                                Option B: In­stead of stor­ing the cus­tomer's in­form­a­tion inside the schedul­ing system, the schedul­ing system could re­quest the SSN and car­rier data from the cus­tomer care system just before it sends the Doctor Visit mes­sage. This solves the first prob­lemwe no longer have to modify the stor­age of the schedul­ing system. How­ever, the second prob­lem re­mains: The schedul­ing system needs to know that the SSN and car­rier in­form­a­tion is re­quired in order to notify the ac­count­ing system. There­fore, the se­mantics of the mes­sage are more sim­ilar to Notify In­sur­ance than to Doctor Visit. In a loosely coupled system, we do not want one system to in­struct the next one on what to do. We in­stead send an Event Mes­sage and let the other sys­tems decide what to do. In ad­di­tion, this solu­tion couples the schedul­ing system more tightly to the cus­tomer care system be­cause the schedul­ing system now needs to know where to get the miss­ing data. This ties the schedul­ing system to both the ac­count­ing system and the cus­tomer care system. This type of coup­ling is un­desir­able be­cause it leads to brittle in­teg­ra­tion solu­tions.

                                                                                                                                                                                Option C: We can avoid some of these de­pend­en­cies if we send the mes­sage to the cus­tomer care system first in­stead of to the ac­count­ing system. The cus­tomer care system can then fetch all the re­quired in­form­a­tion and send a mes­sage with all re­quired data to the ac­count­ing system. This de­couples the schedul­ing system nicely from the sub­se­quent flow of the mes­sage. How­ever, now we im­ple­ment the busi­ness rule that the in­sur­ance com­pany re­ceives a bill after the pa­tient visits the doctor inside the cus­tomer care system. This re­quires us to modify the logic inside the cus­tomer care system. If the cus­tomer care system is a pack­aged ap­plic­a­tion, this modi­fic­a­tion may be dif­fi­cult or im­pos­sible. Even if we can make this modi­fic­a­tion, we now make the cus­tomer care system in­dir­ectly re­spons­ible for send­ing billing mes­sages. This may not be a prob­lem if all the data items re­quired by the ac­count­ing system are avail­able inside the cus­tomer care system. How­ever, if some of the fields have to be re­trieved from other sys­tems, we are in a situ­ation sim­ilar to where we star­ted.

                                                                                                                                                                                Option D (not shown): We could also modify the ac­count­ing system to re­quire only the cus­tomer ID and to re­trieve the SSN and car­rier in­form­a­tion from the cus­tomer care system. This ap­proach has two dis­ad­vant­ages. First, we now couple the ac­count­ing system to the cus­tomer care system. Second, this option again as­sumes that we have con­trol over the ac­count­ing system. In most cases, the ac­count­ing system is a pack­aged ap­plic­a­tion with lim­ited op­tions for cus­tom­iz­a­tion.

                                                                                                                                                                                使用专门的转换器(内容丰富器)来访问外部数据源,以便用缺失的信息来扩充消息。

                                                                                                                                                                                Use a spe­cial­ized trans­former, a Con­tent En­richer, to access an ex­ternal data source in order to aug­ment a mes­sage with miss­ing in­form­a­tion.

                                                                                                                                                                                图形/08inf08.gif



                                                                                                                                                                                内容丰富器使用传入消息内的信息(例如关键字段)从外部源检索数据。内容丰富器从资源中检索所需的数据后,会将数据附加到消息中。传入消息中的原始信息可能会被保留到结果消息中,也可能不再需要,具体取决于接收应用程序的特定需求。

                                                                                                                                                                                The Con­tent En­richer uses in­form­a­tion inside the in­com­ing mes­sage (e.g., key fields) to re­trieve data from an ex­ternal source. After the Con­tent En­richer re­trieves the re­quired data from the re­source, it ap­pends the data to the mes­sage. The ori­ginal in­form­a­tion from the in­com­ing mes­sage may be car­ried over into the res­ult­ing mes­sage or may no longer be needed, de­pend­ing on the spe­cific needs of the re­ceiv­ing ap­plic­a­tion.

                                                                                                                                                                                内容丰富器注入的附加信息必须在系统中的某个位置可用。以下是新数据最常见的来源:

                                                                                                                                                                                The ad­di­tional in­form­a­tion in­jec­ted by the Con­tent En­richer has to be avail­able some­where in the system. Fol­low­ing are the most common sources for the new data:

                                                                                                                                                                                1. 计算: 内容丰富器也许能够计算缺失的信息。在这种情况下,算法包含附加信息。例如,如果接收系统需要城市和州缩写,但传入消息仅包含邮政编码,则算法可以提供城市和州信息。或者,接收系统可能需要指定消息总大小的数据格式。内容丰富器可以添加所有消息字段的长度,从而计算消息大小。这种形式的内容丰富器与基本的消息因为它不需要外部数据源。

                                                                                                                                                                                2. Com­pu­ta­tion: The Con­tent En­richer may be able to com­pute the miss­ing in­form­a­tion. In this case, the al­gorithm in­cor­por­ates the ad­di­tional in­form­a­tion. For ex­ample, if the re­ceiv­ing system re­quires a city and state ab­bre­vi­ation, but the in­com­ing mes­sage con­tains only a ZIP code, the al­gorithm can supply the city and state in­form­a­tion. Or, a re­ceiv­ing system may re­quire a data format that spe­cifies the total size of the mes­sage. The Con­tent En­richer can add the length of all mes­sage fields and thus com­pute the mes­sage size. This form of Con­tent En­richer is very sim­ilar to the basic Mes­sage Trans­lator be­cause it needs no ex­ternal data source.

                                                                                                                                                                                3. 环境: 内容丰富器可能能够从操作环境检索附加数据。最常见的例子是时间戳。例如,接收系统可能要求每个消息携带时间戳。如果发送系统不包含该字段,则内容丰富器可以从操作系统获取当前时间并将其添加到消息中。

                                                                                                                                                                                4. En­vir­on­ment: The Con­tent En­richer may be able to re­trieve the ad­di­tional data from the op­er­at­ing en­vir­on­ment. The most common ex­ample is a time stamp. For ex­ample, the re­ceiv­ing system may re­quire each mes­sage to carry a time stamp. If the send­ing system does not in­clude this field, the Con­tent En­richer can get the cur­rent time from the op­er­at­ing system and add it to the mes­sage.

                                                                                                                                                                                5. 另一种系统: 此选项是最常见的。内容丰富器必须从另一个系统检索丢失的数据。该数据资源可以采用多种形式,包括数据库、文件、LDAP 目录、应用程序或手动输入缺失数据的用户。

                                                                                                                                                                                6. An­other System: This option is the most common one. The Con­tent En­richer has to re­trieve the miss­ing data from an­other system. This data re­source can take on a number of forms, in­clud­ing a data­base, a file, an LDAP dir­ect­ory, an ap­plic­a­tion, or a user who manu­ally enters miss­ing data.

                                                                                                                                                                                在许多情况下,内容丰富器所需的外部资源可能位于另一个系统上,甚至位于企业外部。因此,内容丰富器和资源之间的通信可以通过消息传递或通过任何其他通信机制进行(参见第2章“集成样式”)。由于Content Enricher和数据源之间的交互根据定义是同步的( Content Enricher在数据源返回所请求的数据之前无法发送丰富的消息),同步协议(例如,HTTP 或到数据库的 ODBC 连接)可能会比使用异步消息传递带来更好的性能。内容丰富器和数据源本质上是紧密耦合的,因此通过消息通道实现松散耦合并不那么重要。

                                                                                                                                                                                In many cases, the ex­ternal re­source re­quired by the Con­tent En­richer may be situ­ated on an­other system or even out­side the en­ter­prise. Ac­cord­ingly, the com­mu­nic­a­tion between the Con­tent En­richer and the re­source can occur via Mes­saging or via any other com­mu­nic­a­tion mech­an­ism (see Chapter 2, "In­teg­ra­tion Styles"). Since the in­ter­ac­tion between the Con­tent En­richer and the data source is by defin­i­tion syn­chron­ous (the Con­tent En­richer cannot send the en­riched mes­sage until the data source re­turns the re­ques­ted data), a syn­chron­ous pro­tocol (e.g., HTTP or an ODBC con­nec­tion to a data­base) may result in better per­form­ance than using asyn­chron­ous mes­saging. The Con­tent En­richer and the data source are in­her­ently tightly coupled, so achiev­ing loose coup­ling through Mes­sage Chan­nels is not as im­port­ant.

                                                                                                                                                                                回到我们的示例,我们可以插入内容丰富器以从客户服务系统检索附加数据(见图)。这样,调度系统就可以很好地摆脱处理保险信息或客户服务系统的负担。它所要做的就是发布医生就诊消息。内容丰富器组件负责检索所需的数据。会计系统也独立于客户服务系统。

                                                                                                                                                                                Re­turn­ing to our ex­ample, we can insert a Con­tent En­richer to re­trieve the ad­di­tional data from the cus­tomer care system (see figure). This way, the schedul­ing system is nicely de­coupled from having to deal with in­sur­ance in­form­a­tion or the cus­tomer care system. All it has to do is pub­lish the Doctor Visit mes­sage. The Con­tent En­richer com­pon­ent takes care of re­triev­ing the re­quired data. The ac­count­ing system also re­mains in­de­pend­ent from the cus­tomer care system.

                                                                                                                                                                                将 Enricher 应用于患者示例

                                                                                                                                                                                Ap­ply­ing the En­richer to the Pa­tient Ex­ample

                                                                                                                                                                                图形/08inf09.gif

                                                                                                                                                                                内容丰富器在许多场合用于解析消息中包含的引用。为了使消息保持较小且易于管理,我们通常选择传递对对象的简单引用,而不是传递包含所有数据元素的完整对象。这些引用通常采用密钥或唯一 ID 的形式。当系统需要处理消息时,我们需要根据原始消息中包含的对象引用来检索所需的数据项。我们使用内容丰富器来执行此任务。其中涉及一些明显的权衡。使用引用可以减少原始消息中的数据量,但需要在资源中进行额外的查找。使用引用是否可以提高性能取决于有多少组件可以简单地对引用进行操作,以及有多少组件需要使用内容丰富器来恢复一些原始消息内容。例如,如果消息在到达最终收件人之前经过一长串中介,则使用对象引用可以显着减少消息流量。我们可以插入内容丰富器作为最终收件人将丢失的信息加载到邮件中之前的最后一步。如果消息已经包含我们可能不想一路携带的数据,我们可以使用声明检查来存储数据并获取对其的引用。

                                                                                                                                                                                The Con­tent En­richer is used on many oc­ca­sions to re­solve ref­er­ences con­tained in a mes­sage. In order to keep mes­sages small and easy to manage, we often choose to pass simple ref­er­ences to ob­jects rather than pass a com­plete object with all data ele­ments. These ref­er­ences usu­ally take the form of keys or unique IDs. When the mes­sage needs to be pro­cessed by a system, we need to re­trieve the re­quired data items based on the object ref­er­ences in­cluded in the ori­ginal mes­sage. We use a Con­tent En­richer to per­form this task. There are some ap­par­ent trade-offs in­volved. Using ref­er­ences re­duces the data volume in the ori­ginal mes­sages, but re­quires ad­di­tional look­ups in the re­source. Whether the use of ref­er­ences im­proves per­form­ance de­pends on how many com­pon­ents can op­er­ate simply on ref­er­ences versus how many com­pon­ents need to use a Con­tent En­richer to re­store some of the ori­ginal mes­sage con­tent. For ex­ample, if a mes­sage passes through a long list of in­ter­me­di­ar­ies before it reaches the final re­cip­i­ent, using an object ref­er­ence can de­crease mes­sage traffic sig­ni­fic­antly. We can insert a Con­tent En­richer as the last step before the final re­cip­i­ent to load the miss­ing in­form­a­tion into the mes­sage. If the mes­sage already con­tains data that we might not want to carry along the way, we can use the Claim Check to store the data and obtain a ref­er­ence to it.

                                                                                                                                                                                示例: 与外部各方的沟通

                                                                                                                                                                                Ex­ample: Com­mu­nic­a­tion with Ex­ternal Parties

                                                                                                                                                                                当与要求消息符合特定消息标准(例如,ebXML)的外部各方进行通信时,也通常使用内容丰富器。这些标准中的大多数都需要带有长数据列表的大消息。如果我们保持内部消息尽可能简单,然后每当我们向组织外部发送消息时使用内容丰富器来添加缺少的字段,那么我们通常可以显着简化内部操作。同样,我们可以使用内容过滤器从传入消息中去除不必要的信息(见图)。

                                                                                                                                                                                A Con­tent En­richer is also com­monly used when com­mu­nic­at­ing with ex­ternal parties that re­quire mes­sages to be com­pli­ant with a spe­cific mes­sage stand­ard (e.g., ebXML). Most of these stand­ards re­quire large mes­sages with a long list of data. We can usu­ally sim­plify our in­ternal op­er­a­tions sig­ni­fic­antly if we keep in­ternal mes­sages as simple as pos­sible and then use a Con­tent En­richer to add the miss­ing fields whenever we send a mes­sage out­side of the or­gan­iz­a­tion. Like­wise, we can use a Con­tent Filter to strip un­ne­ces­sary in­form­a­tion from in­com­ing mes­sages (see figure).

                                                                                                                                                                                与外部方通信时使用内容丰富器/内容过滤器对

                                                                                                                                                                                Using a Con­tent En­richer/Con­tent Filter Pair When Com­mu­nic­at­ing with Ex­ternal Parties

                                                                                                                                                                                图形/08inf10.gif



                                                                                                                                                                                  内容过滤器

                                                                                                                                                                                  Content Filter

                                                                                                                                                                                  图形/contentfilter_icon.gif

                                                                                                                                                                                  当消息接收者需要比消息创建者提供的更多或不同的数据元素时,内容丰富器可以帮助我们。令人惊讶的是,在许多情况下都需要相反的效果:从消息中删除数据元素。

                                                                                                                                                                                  The Con­tent En­richer helps us in situ­ations where a mes­sage re­ceiver re­quires moreor dif­fer­ent­data ele­ments than the mes­sage cre­ator provides. There are sur­pris­ingly many situ­ations where the op­pos­ite effect is de­sired: re­mov­ing data ele­ments from a mes­sage.

                                                                                                                                                                                  当您只对少数数据项感兴趣时,如何简化对大消息的处理?

                                                                                                                                                                                  How do you sim­plify deal­ing with a large mes­sage when you are in­ter­ested only in a few data items?



                                                                                                                                                                                  为什么我们要从消息中删除有价值的数据元素?一个常见的原因是安全。从服务请求数据的应用程序可能未被授权查看回复消息包含的所有数据元素。服务提供商可能不了解安全方案并且可能总是返回所有数据元素而不管用户身份。我们需要添加一个步骤,根据请求者经过验证的身份删除敏感数据。例如,工资系统可能仅公开一个返回有关员工的所有数据的简单接口。这些数据可能包括工资信息、社会安全号码和其他敏感信息。如果您正在尝试构建一项返回员工在公司的入职日期的服务,

                                                                                                                                                                                  Why would we want to remove valu­able data ele­ments from a mes­sage? One common reason is se­cur­ity. An ap­plic­a­tion that re­quests data from a ser­vice may not be au­thor­ized to see all data ele­ments that the reply mes­sage con­tains. The ser­vice pro­vider may not have know­ledge of a se­cur­ity scheme and may always return all data ele­ments re­gard­less of user iden­tity. We need to add a step that re­moves sens­it­ive data based on the re­questor's proven iden­tity. For ex­ample, the payroll system may expose only a simple in­ter­face that re­turns all data about an em­ployee. This data may in­clude payroll in­form­a­tion, social se­cur­ity num­bers, and other sens­it­ive in­form­a­tion. If you are trying to build a ser­vice that re­turns an em­ployee's start date with the com­pany, you may want to elim­in­ate all sens­it­ive in­form­a­tion from the result mes­sage before passing it back to the re­questor.

                                                                                                                                                                                  删除数据元素的另一个原因是为了简化消息处理并减少网络流量。在许多情况下,流程是由从业务合作伙伴收到的消息启动的。出于显而易见的原因,希望与第三方的通信基于标准化的消息格式。许多标准机构和委员会为某些行业和应用程序定义了标准 XML 数据格式。著名的例子有 RosettaNet、ebXML、ACORD 等等。虽然这些 XML 格式对于根据商定的标准与外部各方进行交互非常有用,但“委员会设计”方法通常会产生非常大的文档。许多文档都有数百个字段,由多个嵌套级别组成。如此大的文档很难用于内部消息交换。例如,如果要映射的文档有数百个元素,则大多数可视化(拖放式)转换工具将变得无法使用。此外,调试也成为一场重大噩梦。因此,我们希望简化传入文档,使其仅包含内部处理步骤实际需要的元素。从某种意义上说,删除元素丰富了此类消息的有用性,因为删除了冗余和不相关的字段,留下了更有意义的消息,并减少了开发人员犯错的空间。调试成为一场重大噩梦。因此,我们希望简化传入的文档,以仅包含我们内部处理步骤实际需要的元素。从某种意义上说,删除元素丰富了此类消息的有用性,因为删除了冗余和不相关的字段,留下了更有意义的消息,并减少了开发人员犯错的空间。调试成为一场重大噩梦。因此,我们希望简化传入的文档,以仅包含我们内部处理步骤实际需要的元素。从某种意义上说,删除元素丰富了此类消息的有用性,因为删除了冗余和不相关的字段,留下了更有意义的消息,并减少了开发人员犯错的空间。

                                                                                                                                                                                  An­other reason to remove data ele­ments is to sim­plify mes­sage hand­ling and to reduce net­work traffic. In many in­stances, pro­cesses are ini­ti­ated by mes­sages re­ceived from busi­ness part­ners. For ob­vi­ous reas­ons, it is de­sir­able to base com­mu­nic­a­tion with third parties on a stand­ard­ized mes­sage format. A number of stand­ards bodies and com­mit­tees define stand­ard XML data formats for cer­tain in­dus­tries and ap­plic­a­tions. Well-known ex­amples are Roset­taNet, ebXML, ACORD, and many more. While these XML formats are useful to con­duct in­ter­ac­tion with ex­ternal parties based on an agreed-upon stand­ard, the "design-by-com­mit­tee" ap­proach usu­ally res­ults in very large doc­u­ments. Many of the doc­u­ments have hun­dreds of fields, con­sist­ing of mul­tiple nested levels. Such large doc­u­ments are dif­fi­cult to work with for in­ternal mes­sage ex­change. For ex­ample, most visual (drag-drop style) trans­form­a­tion tools become un­us­able if the doc­u­ments to be mapped have hun­dreds of ele­ments. Also, de­bug­ging be­comes a major night­mare. There­fore, we want to sim­plify the in­com­ing doc­u­ments to in­clude only the ele­ments we ac­tu­ally re­quire for our in­ternal pro­cess­ing steps. In a sense, re­mov­ing ele­ments en­riches the use­ful­ness of such a mes­sage, be­cause re­dund­ant and ir­rel­ev­ant fields are re­moved, leav­ing a more mean­ing­ful mes­sage and less room for de­ve­loper mis­takes.

                                                                                                                                                                                  使用内容过滤器从邮件中删除不重要的数据项目,仅保留重要的项目。

                                                                                                                                                                                  Use a Con­tent Filter to remove un­im­port­ant data items from a mes­sage, leav­ing only im­port­ant items.

                                                                                                                                                                                  图形/08inf11.gif



                                                                                                                                                                                  内容过滤器不一定只是删除数据元素。内容过滤器对于简化消息的结构也很有用。通常,消息被表示为树结构。许多源自外部系统或打包应用程序的消息都包含许多级别的嵌套重复组,因为它们是根据通用的标准化数据库结构建模的。通常,已知的约束和假设使得这种级别的嵌套变得多余,并且内容过滤器可用于将层次结构“扁平化”为简单的元素列表,这些元素可以更容易地被其他系统理解和处理。

                                                                                                                                                                                  The Con­tent Filter does not ne­ces­sar­ily just remove data ele­ments. A Con­tent Filter is also useful to sim­plify the struc­ture of the mes­sage. Often, mes­sages are rep­res­en­ted as tree struc­tures. Many mes­sages ori­gin­at­ing from ex­ternal sys­tems or pack­aged ap­plic­a­tions con­tain many levels of nested, re­peat­ing groups be­cause they are modeled after gen­eric, nor­mal­ized data­base struc­tures. Fre­quently, known con­straints and as­sump­tions make this level of nest­ing su­per­flu­ous, and a Con­tent Filter can be used to "flat­ten" the hier­archy into a simple list of ele­ments that can be more easily un­der­stood and pro­cessed by other sys­tems.

                                                                                                                                                                                  使用内容过滤器展平消息层次结构

                                                                                                                                                                                  Flat­ten­ing a Mes­sage Hier­archy with a Con­tent Filter

                                                                                                                                                                                  图形/08inf12.gif

                                                                                                                                                                                  多个内容过滤器可以用作静态拆分器(请参阅拆分器) ,将一条复杂的消息分解为单独的消息,每个消息处理大消息的某个方面(请参阅下一页顶部的图)。

                                                                                                                                                                                  Mul­tiple Con­tent Filters can be used as a static Split­ter (see Split­ter) to break one com­plex mes­sage into in­di­vidual mes­sages that each deal with a cer­tain aspect of the large mes­sage (see figure at the top of the next page).

                                                                                                                                                                                  使用多个内容过滤器作为静态拆分器

                                                                                                                                                                                  Using Mul­tiple Con­tent Fil­ters as a Static Split­ter

                                                                                                                                                                                  图形/08inf13.gif

                                                                                                                                                                                  示例: 数据库适配器

                                                                                                                                                                                  Ex­ample: Data­base Ad­apter

                                                                                                                                                                                  许多集成套件提供通道适配器来连接到现有系统。在许多情况下,这些适配器发布的消息的格式类似于应用程序的内部结构。例如,假设我们将数据库适配器连接到具有以下架构的数据库:

                                                                                                                                                                                  Many in­teg­ra­tion suites provide Chan­nel Ad­apters to con­nect to ex­ist­ing sys­tems. In many cases, these ad­apters pub­lish mes­sages whose format re­sembles the in­ternal struc­ture of the ap­plic­a­tion. For ex­ample, let's assume we con­nect a data­base ad­apter to a data­base with the fol­low­ing schema:

                                                                                                                                                                                  数据库架构示例

                                                                                                                                                                                  An Ex­ample Data­base Schema

                                                                                                                                                                                  图形/08inf14.gif

                                                                                                                                                                                  物理数据库模式最好将相关实体存储在通过外键和关系表链接的单独表中(例如,ACCOUNT_CONTACT链接ACCOUNTCONTACT表) 。许多商业数据库适配器将这些相关表转换为分层消息结构,该结构可以包含可能与消息接收者不相关的附加字段,例如主键和外键。为了使处理消息更容易,我们可以使用内容过滤器来扁平化消息结构并仅提取相关字段。该示例显示了内容过滤器的实现使用视觉转换工具。我们可以看到如何将消息从分布在多个级别的十多个字段减少为具有五个字段的简单消息。其他组件使用简化的消息会更容易(也更有效)。

                                                                                                                                                                                  It is de­sir­able for a phys­ical data­base schema to store re­lated en­tit­ies in sep­ar­ate tables that are linked by for­eign keys and re­la­tion tables (e.g., AC­COUN­T_­CON­TACT links the AC­COUNT and CON­TACT tables). Many com­mer­cial data­base ad­apters trans­late these re­lated tables into a hier­arch­ical mes­sage struc­ture that can con­tain ad­di­tional fields such as primary and for­eign keys that may not be rel­ev­ant to the mes­sage re­ceiver. In order to make pro­cess­ing a mes­sage easier, we can use a Con­tent Filter to flat­ten the mes­sage struc­ture and ex­tract only rel­ev­ant fields. The ex­ample shows the im­ple­ment­a­tion of a Con­tent Filter using a visual trans­form­a­tion tool. We can see how we reduce the mes­sage from over a dozen fields spread across mul­tiple levels into a simple mes­sage with five fields. It will be much easier (and more ef­fi­cient) for other com­pon­ents to work with the sim­pli­fied mes­sage.

                                                                                                                                                                                  简化数据库适配器发布的消息

                                                                                                                                                                                  Sim­pli­fy­ing a Mes­sage Pub­lished by a Data­base Ad­apter

                                                                                                                                                                                  图形/08inf15.gif

                                                                                                                                                                                  内容过滤器并不是解决此特定问题的唯一方法。例如,我们可以在数据库中配置一个视图来解析表关系并返回一个简单的结果集。如果我们能够向数据库添加视图,这可能是一个简单的选择。在许多情况下,企业集成的目标是尽可能减少侵入性,并且该准则可能包括不向数据库添加视图。

                                                                                                                                                                                  A Con­tent Filter is not the only solu­tion to this par­tic­u­lar prob­lem. For ex­ample, we could con­fig­ure a view in the data­base that re­solves the table re­la­tion­ships and re­turns a simple result set. This may be a simple choice if we have the abil­ity to add views to the data­base. In many situ­ations, en­ter­prise in­teg­ra­tion aims to be as little in­trus­ive as pos­sible and that guideline may in­clude not adding views to a data­base.



                                                                                                                                                                                    索赔支票

                                                                                                                                                                                    Claim Check

                                                                                                                                                                                    图形/claimcheck_icon.gif

                                                                                                                                                                                    内容丰富器告诉我们如何处理消息缺少所需数据项的情况。为了达到相反的效果,内容过滤器允许我们从消息中删除不感兴趣的数据项。但是,我们可能只想暂时删除字段。例如,消息可能包含消息流稍后需要的一组数据项,但并非所有中间处理步骤都需要这些数据项。我们可能不想在每个处理步骤中携带所有这些信息,因为这可能会导致性能下降,并且由于我们携带了太多额外的数据,因此使调试变得更加困难。

                                                                                                                                                                                    The Con­tent En­richer tells us how we can deal with situ­ations where our mes­sage is miss­ing re­quired data items. To achieve the op­pos­ite effect, the Con­tent Filter lets us remove un­in­ter­est­ing data items from a mes­sage. How­ever, we may want to remove fields only tem­por­ar­ily. For ex­ample, a mes­sage may con­tain a set of data items that are needed later in the mes­sage flow but that are not ne­ces­sary for all in­ter­me­di­ate pro­cess­ing steps. We may not want to carry all this in­form­a­tion through each pro­cess­ing step, be­cause it can cause per­form­ance de­grad­a­tion, and it makes de­bug­ging harder be­cause we carry so much extra data.

                                                                                                                                                                                    如何在不牺牲信息内容的情况下减少整个系统发送的消息的数据量?

                                                                                                                                                                                    How can we reduce the data volume of a mes­sage sent across the system without sac­ri­fi­cing in­form­a­tion con­tent?



                                                                                                                                                                                    通过消息移动大量数据可能效率低下。一些消息传递系统甚至对消息的大小有硬性限制。其他消息传递系统使用数据的 XML 表示形式,这可能会将消息的大小增加一个数量级或更多。因此,虽然消息传递提供了最可靠、响应最快的信息传输方式,但它可能不是最有效的。

                                                                                                                                                                                    Moving large amounts of data via mes­sages may be in­ef­fi­cient. Some mes­saging sys­tems even have hard limits as to the size of mes­sages. Other mes­saging sys­tems use an XML rep­res­ent­a­tion of data, which can in­crease the size of a mes­sage by an order of mag­nitude or more. So, while mes­saging provides the most re­li­able and re­spons­ive way to trans­mit in­form­a­tion, it may not be the most ef­fi­cient.

                                                                                                                                                                                    此外,在消息中携带较少的数据可以防止中间系统依赖于不适合它们的信息。例如,如果我们通过一系列中间体将地址信息从一个系统发送到另一个系统,则中间体可能会开始对地址数据做出假设,从而变得依赖于消息数据格式。使消息尽可能小可以减少引入此类隐藏假设的可能性。

                                                                                                                                                                                    Also, car­ry­ing less data in a mes­sage keeps in­ter­me­di­ate sys­tems from de­pend­ing on in­form­a­tion that was not in­ten­ded for them. For ex­ample, if we send ad­dress in­form­a­tion from one system to an­other via a series of in­ter­me­di­ates, the in­ter­me­di­ates could start making as­sump­tions about the ad­dress data and there­fore become de­pend­ent on the mes­sage data format. Making mes­sages as small as pos­sible re­duces the pos­sib­il­ity of in­tro­du­cing such hidden as­sump­tions.

                                                                                                                                                                                    简单的内容过滤器可以帮助我们减少数据量,但不能保证我们以后可以恢复消息内容。因此,我们需要以一种稍后可以检索的方式存储完整的消息信息。

                                                                                                                                                                                    A simple Con­tent Filter helps us reduce data volume but does not guar­an­tee that we can re­store the mes­sage con­tent later on. There­fore, we need to store the com­plete mes­sage in­form­a­tion in a way that we can re­trieve it later.

                                                                                                                                                                                    因为我们需要存储每条消息的数据,所以我们需要一个密钥来检索与消息关联的正确数据项。我们可以使用消息 ID 作为密钥,但这不允许后续组件传递密钥,因为消息 ID 会随着每条消息而变化。

                                                                                                                                                                                    Be­cause we need to store data for each mes­sage, we need a key to re­trieve the cor­rect data items as­so­ci­ated with a mes­sage. We could use the mes­sage ID as the key, but that would not allow sub­se­quent com­pon­ents to pass the key on, be­cause the mes­sage ID changes with each mes­sage.

                                                                                                                                                                                    将消息数据存储在持久存储中并将声明检查传递给后续组件。这些组件可以使用声明检查来检索存储的信息。

                                                                                                                                                                                    Store mes­sage data in a per­sist­ent store and pass a Claim Check to sub­se­quent com­pon­ents. These com­pon­ents can use the Claim Check to re­trieve the stored in­form­a­tion.

                                                                                                                                                                                    图形/08inf16.gif



                                                                                                                                                                                    声明检查模式包含以下步骤:

                                                                                                                                                                                    The Claim Check pat­tern con­sists of the fol­low­ing steps:

                                                                                                                                                                                    1. 带有数据的消息到达。

                                                                                                                                                                                    2. A mes­sage with data ar­rives.

                                                                                                                                                                                    3. 检查行李组件生成该信息的唯一密钥。该密钥稍后将用作索赔检查。

                                                                                                                                                                                    4. The Check Lug­gage com­pon­ent gen­er­ates a unique key for the in­form­a­tion. This key will be used later as the Claim Check.

                                                                                                                                                                                    5. 检查行李组件从消息中提取数据并将其存储在持久存储中,例如文件或数据库。它将存储的数据与密钥相关联。

                                                                                                                                                                                    6. The Check Lug­gage com­pon­ent ex­tracts the data from the mes­sage and stores it in a per­sist­ent store, such as a file or a data­base. It as­so­ci­ates the stored data with the key.

                                                                                                                                                                                    7. 它从消息中删除持久数据并添加声明检查

                                                                                                                                                                                    8. It re­moves the per­sisted data from the mes­sage and adds the Claim Check.

                                                                                                                                                                                    9. 另一个组件可以使用内容丰富器来检索基于声明

                                                                                                                                                                                    10. An­other com­pon­ent can use a Con­tent En­richer to re­trieve the data based on the Claim Check.

                                                                                                                                                                                    此过程类似于机场的行李检查。如果您不想随身携带所有行李,只需在航空公司柜台托运即可。作为回报,您会在机票上收到一张贴纸,上面有一个参考号,可以唯一标识您托运的每件行李。到达最终目的地后,您可以取回行李。

                                                                                                                                                                                    This pro­cess is ana­log­ous to a lug­gage check at the air­port. If you do not want to carry all of your lug­gage with you, you simply check it at the air­line counter. In return, you re­ceive a sticker on your ticket that has a ref­er­ence number that uniquely iden­ti­fies each piece of lug­gage you checked. Once you reach your final des­tin­a­tion, you can re­trieve your lug­gage.

                                                                                                                                                                                    如图所示,原始消息中包含的数据仍然需要“移动”到最终目的地。那么,我们有什么收获吗?是的,因为通过消息传递传输数据可能比将数据存储在中央数据存储中效率低。例如,消息可以经历不需要大量数据的多个路由步骤。使用消息传递系统,消息数据将在每一步被编组和解编,可能被加密和解密。这种类型的操作可能非常消耗 CPU 资源,并且对于任何中间步骤都不需要而仅最终目的地需要的数据来说是完全没有必要的。索赔检查在消息经过多个组件并返回到发送者的情况下也能很好地工作。在这种情况下,检查行李组件和内容丰富器位于同一组件的本地,并且数据永远不必通过网络传输(见图):

                                                                                                                                                                                    As the figure il­lus­trates, the data that was con­tained in the ori­ginal mes­sage still needs to be "moved" to the ul­ti­mate des­tin­a­tion. So, did we gain any­thing? Yes, be­cause trans­port­ing data via mes­saging may be less ef­fi­cient than stor­ing it in a cent­ral data­store. For ex­ample, the mes­sage may un­dergo mul­tiple rout­ing steps that do not re­quire the large amount of data. Using a mes­saging system, the mes­sage data would be mar­shaled and un­mar­shaled, pos­sibly en­cryp­ted and de­cryp­ted, at every step. This type of op­er­a­tion can be very CPU-in­tens­ive and would be com­pletely un­ne­ces­sary for data that is not needed by any in­ter­me­di­ate step but only by the final des­tin­a­tion. The Claim Check also works well in a scen­ario where a mes­sage travels through a number of com­pon­ents and re­turns to the sender. In this case, the Check Lug­gage com­pon­ent and the Con­tent En­richer are local to the same com­pon­ent, and the data never has to travel across the net­work (see figure):

                                                                                                                                                                                    数据可以在本地存储和检索

                                                                                                                                                                                    The Data May Be Stored and Re­trieved Loc­ally

                                                                                                                                                                                    图形/08inf17.gif

                                                                                                                                                                                    选择一把钥匙

                                                                                                                                                                                    Choos­ing a Key

                                                                                                                                                                                    我们应该如何为数据选择密钥?我的脑海中浮现出许多选项:

                                                                                                                                                                                    How should we choose a key for the data? A number of op­tions spring to mind:

                                                                                                                                                                                    1. 业务密钥(例如客户 ID)可能已包含在消息正文中。

                                                                                                                                                                                    2. A busi­ness key, such as a cus­tomer ID, may already be con­tained in the mes­sage body.

                                                                                                                                                                                    3. 该消息可以包含可用于将数据存储中的数据与该消息相关联的消息ID。

                                                                                                                                                                                    4. The mes­sage may con­tain a mes­sage ID that can be used to as­so­ci­ate the data in the data­store with the mes­sage.

                                                                                                                                                                                    5. 我们可以生成一个唯一的ID。

                                                                                                                                                                                    6. We can gen­er­ate a unique ID.

                                                                                                                                                                                    重用现有的业务密钥似乎是最简单的选择。如果我们必须隐藏一些客户详细信息,我们可以稍后通过客户 ID 来引用它。当我们将此密钥传递给其他组件时,我们需要决定是否希望这些组件知道该密钥是客户 ID 而不仅仅是抽象密钥。将键表示为抽象键的优点是我们可以以相同的方式处理所有键,并且可以创建通用机制来基于抽象键从数据存储中检索数据。

                                                                                                                                                                                    Re­using an ex­ist­ing busi­ness key seems like the easi­est choice. If we have to stow away some cus­tomer detail, we can ref­er­ence it later by the cus­tomer ID. When we pass this key to other com­pon­ents, we need to decide whether we want these com­pon­ents to be aware that the key is a cus­tomer ID as op­posed to just an ab­stract key. Rep­res­ent­ing the key as an ab­stract key has the ad­vant­age that we can pro­cess all keys in the same way and can create a gen­eric mech­an­ism to re­trieve data from the data­store based on an ab­stract key.

                                                                                                                                                                                    使用消息 ID 作为密钥似乎很方便,但通常不是一个好主意。使用消息 ID 作为数据检索的密钥会导致双重语义附加到单个数据元素,并可能导致冲突。例如,假设我们需要将Claim Check引用传递给另一条消息。新消息应该被分配一个新的、唯一的 ID,但是我们不能再使用该新 ID 从数据存储中检索数据。仅当我们希望数据只能在单个消息的范围内访问时,消息 ID 的使用才有意义。因此,一般来说,最好分配一个新元素来保存密钥,并避免这种不良形式的“元素重用”。

                                                                                                                                                                                    Using the mes­sage ID as the key may seem con­veni­ent but is gen­er­ally not a good idea. Using a mes­sage ID as a key for data re­trieval res­ults in dual se­mantics being at­tached to a single data ele­ment and can cause con­flicts. For ex­ample, let's assume we need to pass the Claim Check ref­er­ence on to an­other mes­sage. The new mes­sage is sup­posed to be as­signed a new, unique ID, but then we can't use that new ID any­more to re­trieve data from the data­store. The use of a mes­sage ID can be mean­ing­ful only in a cir­cum­stance where we want the data to be ac­cess­ible only within the scope of the single mes­sage. There­fore, in gen­eral it is better to assign a new ele­ment to hold the key and avoid this bad form of "ele­ment reuse."

                                                                                                                                                                                    数据可能仅临时存储在数据存储中。我们如何删除未使用的数据?我们可以修改从数据存储中检索数据的语义,以便在读取数据时删除数据。在这种情况下,我们只能检索数据一次,出于安全原因,这在某些情况下实际上可能是可取的。但是,它不允许多个组件访问相同的数据。或者,我们可以为数据附加一个过期日期,并定义一个垃圾收集过程,定期删除超过一定期限的所有数据。作为第三种选择,我们可能不想删除任何数据。出现这种情况可能是因为我们使用业务系统作为数据存储(例如会计系统)并且需要维护该系统中的所有数据。

                                                                                                                                                                                    The data may be stored in the data­store only tem­por­ar­ily. How do we remove unused data? We can modify the se­mantics of the data re­trieval from the data­store to delete the data when it is read. In this case, we can re­trieve the data only once, which may ac­tu­ally be de­sir­able in some cases for se­cur­ity reas­ons. How­ever, it does not allow mul­tiple com­pon­ents to access the same data. Al­tern­at­ively, we can attach an ex­pir­a­tion date to the data and define a garbage col­lec­tion pro­cess that peri­od­ic­ally re­moves all data over a cer­tain age. As a third option, we may not want to remove any data. This may be the case be­cause we use a busi­ness system as the data­store (e.g., an ac­count­ing system) and need to main­tain all data in that system.

                                                                                                                                                                                    数据存储的实现可以采用多种形式。数据库是一个显而易见的选择,但一组 XML 文件或内存中的消息存储也可以用作数据存储。有时,我们可能会使用应用程序作为数据存储。重要的是,集成解决方案其他部分中的组件可以访问数据存储,以便这些部分可以重建原始消息。

                                                                                                                                                                                    The im­ple­ment­a­tion of a data­store can take on vari­ous forms. A data­base is an ob­vi­ous choice, but a set of XML files or an in-memory mes­sage store can serve as a data­store just as well. Some­times, we may use an ap­plic­a­tion as the data­store. It is im­port­ant that the data­store is reach­able by com­pon­ents in other parts of the in­teg­ra­tion solu­tion so that these parts can re­con­struct the ori­ginal mes­sage.

                                                                                                                                                                                    使用索赔检查来隐藏信息

                                                                                                                                                                                    Using a Claim Check to Hide In­form­a­tion

                                                                                                                                                                                    虽然索赔检查的初衷除了避免发送大量数据之外,它还可以用于其他目的。通常,我们希望在向外部方发送消息之前删除敏感数据(见图)。这意味着外部各方仅在需要知道的基础上接收数据。例如,当我们将员工数据发送给外部方时,我们可能更愿意通过一些神奇的唯一 ID 来引用员工,并消除社会安全号码等字段。外部方完成所需的处理后,我们通过合并数据存储中的数据和外部方返回的消息来重建完整的消息。我们甚至可以为这些消息生成特殊的唯一密钥,以便我们限制外部方通过其拥有的密钥可以采取的操作。这将限制外部人员将消息恶意输入到我们的系统中。包含无效(或过期或已使用)密钥的消息将被阻止尝试使用错误密钥检索消息数据时内容丰富。

                                                                                                                                                                                    While the ori­ginal intent of the Claim Check is to avoid send­ing around large volumes of data, it can also serve other pur­poses. Often, we want to remove sens­it­ive data before send­ing a mes­sage to an out­side party (see figure). This means that out­side parties re­ceive data only on a need-to-know basis. For ex­ample, when we send em­ployee data to an ex­ternal party, we may prefer to ref­er­ence em­ploy­ees by some magic unique ID and elim­in­ate fields such as social se­cur­ity number. After the out­side party has com­pleted the re­quired pro­cess­ing, we re­con­struct the com­plete mes­sage by mer­ging data from the data­store and the mes­sage re­turned from the out­side party. We may even gen­er­ate spe­cial unique keys for these mes­sages so that we re­strict the ac­tions the out­side party can take by the key it pos­sesses. This will re­strict the out­side party from ma­li­ciously feed­ing mes­sages into our system. Mes­sages con­tain­ing an in­valid (or ex­pired or already used) key will be blocked by the Con­tent En­richer when at­tempt­ing to re­trieve mes­sage data using the bad key.

                                                                                                                                                                                    消除可信进程边界之外的敏感消息数据

                                                                                                                                                                                    Elim­in­at­ing Sens­it­ive Mes­sage Data Out­side of the Trus­ted Pro­cess Bound­ary

                                                                                                                                                                                    图形/08inf18.gif

                                                                                                                                                                                    使用流程管理器进行索赔检查

                                                                                                                                                                                    Using a Pro­cess Man­ager with a Claim Check

                                                                                                                                                                                    如果我们与多个外部方交互,流程管理器可以提供索赔检查的功能。当消息到达时,流程管理器创建流程实例(有时称为任务或作业)。流程管理器允许将附加数据与每个单独的流程实例相关联。实际上,流程引擎现在充当数据存储区,存储我们的消息数据。这使得流程管理器向外部各方发送仅包含与该方相关数据的消息。这些消息不必携带原始消息中包含的所有信息,因为该信息保存在进程的数据存储中。当流程管理器收到来自外部方的响应消息时,它将新数据与流程实例已存储的数据合并。

                                                                                                                                                                                    If we in­ter­act with more than one ex­ternal party, a Pro­cess Man­ager can provide the func­tion of a Claim Check. A Pro­cess Man­ager cre­ates pro­cess in­stances (some­times called tasks or jobs) when a mes­sage ar­rives. The Pro­cess Man­ager allows ad­di­tional data to be as­so­ci­ated with each in­di­vidual pro­cess in­stance. In effect, the pro­cess engine now serves as the data­store, stor­ing our mes­sage data. This allows the Pro­cess Man­ager to send mes­sages to ex­ternal parties that con­tain only the data rel­ev­ant to that party. The mes­sages do not have to carry all the in­form­a­tion con­tained in the ori­ginal mes­sage, since that in­form­a­tion is kept with the pro­cess's data­store. When the Pro­cess Man­ager re­ceives a re­sponse mes­sage from an ex­ternal party, it merges the new data with the data already stored by the pro­cess in­stance.

                                                                                                                                                                                    在流程管理器中存储数据

                                                                                                                                                                                    Stor­ing Data Inside a Pro­cess Man­ager

                                                                                                                                                                                    图形/08inf19.gif

                                                                                                                                                                                      标准化器

                                                                                                                                                                                      Normalizer

                                                                                                                                                                                      图形/normalizer_icon.gif

                                                                                                                                                                                      在企业对企业(B2B)集成场景中,企业接收来自不同业务伙伴的消息是很常见的。这些消息可能具有相同的含义,但遵循不同的格式,具体取决于合作伙伴的内部系统和偏好。例如,在 ThoughtWorks,我们为按次付费提供商构建了一个解决方案,该提供商必须接受和处理来自 1,700 多个附属机构的收视信息,其中大多数不符合标准格式。

                                                                                                                                                                                      In a busi­ness-to-busi­ness (B2B) in­teg­ra­tion scen­ario, it is quite common for an en­ter­prise to re­ceive mes­sages from dif­fer­ent busi­ness part­ners. These mes­sages may have the same mean­ing but follow dif­fer­ent formats, de­pend­ing on the part­ners' in­ternal sys­tems and pref­er­ences. For ex­ample, at Thought­Works we built a solu­tion for a pay-per-view pro­vider that has to accept and pro­cess view­er­ship in­form­a­tion from over 1,700 af­fil­i­ates, most of which did not con­form to a stand­ard format.

                                                                                                                                                                                      如何处理语义上相同但以不同格式到达的消息?

                                                                                                                                                                                      How do you pro­cess mes­sages that are se­mantic­ally equi­val­ent but arrive in a dif­fer­ent format?



                                                                                                                                                                                      从技术角度来看,最简单的解决方案似乎为所有参与者规定了统一的格式。如果企业是一家大公司并且拥有 B2B 交易或供应渠道的控制权,这可能会起作用。例如,如果通用汽车希望以通用消息格式接收来自其供应商的订单状态更新,我们可以非常确定乔的供应商业务很可能符合通用汽车的准则。然而,在许多其他情况下,企业不会有这样的奢侈。相反,许多业务模型将消息接收者定位为信息的“聚合器”,并且与各个参与者达成的协议的一部分是需要对其系统基础设施进行最少的更改。因此,

                                                                                                                                                                                      The easi­est solu­tion from a tech­nical per­spect­ive may seem to dic­tate a uni­form format on all par­ti­cipants. This may work if the busi­ness is a large cor­por­a­tion and has con­trol over the B2B ex­change or the supply chan­nel. For ex­ample, if Gen­eral Motors would like to re­ceive order status up­dates from its sup­pli­ers in a common mes­sage format, we can be pretty sure that Joe's Sup­plier busi­ness is likely to con­form to GM's guidelines. In many other situ­ations, how­ever, a busi­ness is not going to have such a luxury. On the con­trary, many busi­ness models po­s­i­tion the mes­sage re­cip­i­ent as an "ag­greg­ator" of in­form­a­tion, and part of the agree­ment with the in­di­vidual par­ti­cipants is that a min­imum of changes is re­quired to their sys­tems in­fra­struc­ture. As a result, you find the ag­greg­ator will­ing to pro­cess in­form­a­tion ar­riv­ing in any data format ran­ging from EDI re­cords or comma-sep­ar­ated files to XML doc­u­ments or Excel spread­sheets ar­riv­ing via e-mail.

                                                                                                                                                                                      与众多合作伙伴打交道时的一个重要考虑因素是变化率。每个参与者不仅可能一开始就喜欢不同的数据格式,而且首选格式也可能随着时间的推移而改变。此外,新参与者可能会加入,而其他参与者可能会退出。即使特定合作伙伴每隔几年才对数据格式进行一次更改,与几十个合作伙伴打交道也可能很快导致每月或每周发生更改。重要的是尽可能将这些更改与其余处理隔离开来,以避免更改在整个系统中产生“连锁反应”。

                                                                                                                                                                                      One im­port­ant con­sid­er­a­tion when deal­ing with a mul­ti­tude of part­ners is the rate of change. Not only may each par­ti­cipant prefer a dif­fer­ent data format to begin with, but the pre­ferred format may also change over time. In ad­di­tion, new par­ti­cipants may join while others drop off. Even if a spe­cific part­ner makes changes to the data format only once every couple of years, deal­ing with a few dozen part­ners can quickly result in monthly or weekly changes. It is im­port­ant to isol­ate these changes from the rest of the pro­cess­ing as much as pos­sible to avoid a "ripple effect" of changes through­out the whole system.

                                                                                                                                                                                      为了将系统的其余部分与各种传入消息格式隔离,您需要将传入消息转换为通用格式。由于传入消息的类型不同,因此每种消息数据格式都需要不同的消息转换器。实现这一点的最简单方法是使用一组Datatype Channels ,每个消息类型对应一个 Datatype Channels 。然后,每个数据类型通道都连接到不同的消息转换器。这种方法的缺点是大量的消息格式会转化为同样大量的消息通道

                                                                                                                                                                                      To isol­ate the re­mainder of the system from the vari­ety of in­com­ing mes­sage formats, you need to trans­form the in­com­ing mes­sages into a common format. Be­cause the in­com­ing mes­sages are of dif­fer­ent types, you need a dif­fer­ent Mes­sage Trans­lator for each mes­sage data format. The easi­est way to ac­com­plish this is to use a col­lec­tion of Data­type Chan­nels, one for each mes­sage type. Each Data­type Chan­nel is then con­nec­ted to a dif­fer­ent Mes­sage Trans­lator. The draw­back of this ap­proach is that a large number of mes­sage formats trans­lates into an equally large number of Mes­sage Chan­nels.

                                                                                                                                                                                      使用规范化器通过自定义消息转换器路由每种消息类型,以便生成的消息与通用格式匹配。

                                                                                                                                                                                      Use a Nor­mal­izer to route each mes­sage type through a custom Mes­sage Trans­lator so that the res­ult­ing mes­sages match a common format.

                                                                                                                                                                                      图形/08inf20.gif



                                                                                                                                                                                      规范化器每种消息格式配备一个消息转换器,并通过消息路由器将传入消息路由到正确的消息转换器

                                                                                                                                                                                      The Nor­mal­izer fea­tures one Mes­sage Trans­lator for each mes­sage format and routes the in­com­ing mes­sage to the cor­rect Mes­sage Trans­lator via a Mes­sage Router.

                                                                                                                                                                                      检测消息格式

                                                                                                                                                                                      De­tect­ing the Mes­sage Format

                                                                                                                                                                                      将消息路由到正确的消息转换器假定消息路由器可以检测传入消息的类型。许多消息传递系统在消息头中为每条消息配备了类型说明符字段,以使此类任务变得简单。然而,在许多 B2B 场景中,消息并不以符合企业内部消息传递系统的消息形式到达,而是以多种格式到达,例如逗号分隔的文件或没有关联模式的 XML 文档。虽然为任何传入数据格式配备类型说明符无疑是最佳实践,但我们非常清楚,世界远非完美。因此,我们需要考虑更通用的方法来识别传入消息的格式。无模式 XML 文档的一种常见方法是使用根元素的名称来假定正确的类型。如果多种数据格式使用相同的根元素,您可以使用 XPath 表达式来确定特定子节点的存在。以逗号分隔的文件可能需要更多的创造力。有时,您可以根据字段数量和数据类型(例如数字与字符串)来确定类型。如果数据以文件形式到达,最简单的方法可能是使用文件名或文件夹结构作为替代数据类型 通道。每个业务合作伙伴都可以使用唯一的命名约定来命名该文件。然后,消息路由器可以使用该文件名将消息路由到适当的消息转换器

                                                                                                                                                                                      Rout­ing the mes­sage to the cor­rect Mes­sage Trans­lator as­sumes that the Mes­sage Router can detect the type of the in­com­ing mes­sage. Many mes­saging sys­tems equip each mes­sage with a type spe­cifier field in the mes­sage header to make this type of task simple. How­ever, in many B2B scen­arios, mes­sages do not arrive as mes­sages com­pli­ant with the en­ter­prise's in­ternal mes­saging system, but in di­verse formats such as comma-sep­ar­ated files or XML doc­u­ments without as­so­ci­ated schema. While it is cer­tainly best prac­tice to equip any in­com­ing data format with a type spe­cifier, we know all too well that the world is far from per­fect. As a result, we need to think of more gen­eral ways to identify the format of the in­com­ing mes­sage. One common way for schema-less XML doc­u­ments is to use the name of the root ele­ment to assume the cor­rect type. If mul­tiple data formats use the same root ele­ment, you can use XPath ex­pres­sions to de­term­ine the pres­ence of spe­cific sub­nodes. Comma-sep­ar­ated files can re­quire a little more cre­ativ­ity. Some­times you can de­term­ine the type based on the number of fields and the type of the data (e.g., nu­meric vs. string). If the data ar­rives as files, the easi­est way may be to use the file name or the file folder struc­ture as a sur­rog­ate Data­type Chan­nel. Each busi­ness part­ner can name the file with a unique naming con­ven­tion. The Mes­sage Router can then use the file name to route the mes­sage to the ap­pro­pri­ate Mes­sage Trans­lator.

                                                                                                                                                                                      使用消息路由器还允许将相同的转换用于多个业务合作伙伴。如果多个业务合作伙伴使用相同的格式或者转换足够通用以适应多种消息格式,这可能会很有用。例如,XPath 表达式非常适合从 XML 文档中挑选元素,即使文档的格式有所不同。

                                                                                                                                                                                      The use of a Mes­sage Router also allows the same trans­form­a­tion to be used for mul­tiple busi­ness part­ners. That might be useful if mul­tiple busi­ness part­ners use the same format or if a trans­form­a­tion is gen­eric enough to ac­com­mod­ate mul­tiple mes­sage formats. For ex­ample, XPath ex­pres­sions are great at pick­ing out ele­ments from XML doc­u­ments even if the doc­u­ments vary in format.

                                                                                                                                                                                      由于规范化器在消息传递解决方案中很常见,因此我们为其创建了一个简写图标:

                                                                                                                                                                                      Since a Nor­mal­izer is a common oc­cur­rence in mes­saging solu­tions, we cre­ated a short­hand icon for it:

                                                                                                                                                                                      规范化器的实际应用

                                                                                                                                                                                      The Nor­mal­izer in Action

                                                                                                                                                                                      图形/08inf21.gif

                                                                                                                                                                                        规范数据模型

                                                                                                                                                                                        Canonical Data Model

                                                                                                                                                                                        我们正在设计几个通过消息传递协同工作的应用程序。 每个应用程序都有自己的内部数据格式。

                                                                                                                                                                                        We are design­ing sev­eral ap­plic­a­tions to work to­gether through Mes­saging. Each ap­plic­a­tion has its own in­ternal data format.

                                                                                                                                                                                        在集成使用不同数据格式的应用程序时,如何最大限度地减少依赖性?

                                                                                                                                                                                        How can you min­im­ize de­pend­en­cies when in­teg­rat­ing ap­plic­a­tions that use dif­fer­ent data formats?



                                                                                                                                                                                        独立开发的应用程序往往使用不同的数据格式,因为每种格式的设计都只考虑了该应用程序。当应用程序被设计为向某个未知应用程序发送消息或从某些未知应用程序接收消息时,该应用程序自然会使用对其最方便的消息格式。同样,用于集成打包应用程序的商业适配器通常以类似于应用程序内部数据结构的数据格式发布和使用消息。

                                                                                                                                                                                        In­de­pend­ently de­ve­loped ap­plic­a­tions tend to use dif­fer­ent data formats be­cause each format was de­signed with just that ap­plic­a­tion in mind. When an ap­plic­a­tion is de­signed to send mes­sages to or re­ceive mes­sages from some un­known ap­plic­a­tion, the ap­plic­a­tion will nat­ur­ally use the mes­sage format that is most con­veni­ent for it. Like­wise, com­mer­cial ad­apters used to in­teg­rate pack­aged ap­plic­a­tions typ­ic­ally pub­lish and con­sume mes­sages in a data format that re­sembles the ap­plic­a­tion's in­ternal data struc­ture.

                                                                                                                                                                                        消息转换器解决消息格式的差异,而无需更改应用程序或让应用程序了解彼此的数据格式。但是,如果大量应用程序相互通信,则每对通信应用程序之间可能需要一个消息转换器(见图)。

                                                                                                                                                                                        The Mes­sage Trans­lator re­solves dif­fer­ences in mes­sage formats without chan­ging the ap­plic­a­tions or having the ap­plic­a­tions know about each other's data formats. How­ever, if a large number of ap­plic­a­tions com­mu­nic­ate with each other, one Mes­sage Trans­lator may be needed between each pair of com­mu­nic­at­ing ap­plic­a­tions (see figure).

                                                                                                                                                                                        随着系统数量的增加,连接数量呈爆炸式增长

                                                                                                                                                                                        The Number of Con­nec­tions Ex­plodes with an In­creas­ing Number of Sys­tems

                                                                                                                                                                                        图形/08inf22.gif

                                                                                                                                                                                        这种方法需要大量的消息转换器,特别是考虑到每个集成应用程序可能发布或使用多种消息类型时。所需消息转换器的数量随着集成应用程序的数量呈指数级增长,这很快就会变得难以管理。

                                                                                                                                                                                        This ap­proach re­quires a large number of Mes­sage Trans­lat­ors, es­pe­cially when con­sid­er­ing that each in­teg­rated ap­plic­a­tion may pub­lish or con­sume mul­tiple mes­sage types. The number of re­quired Mes­sage Trans­lat­ors in­creases ex­po­nen­tially with the number of in­teg­rated ap­plic­a­tions, which quickly be­comes un­man­age­able.

                                                                                                                                                                                        虽然消息转换器提供了两个通信应用程序使用的消息格式之间的间接转换,但它仍然依赖于任一应用程序使用的消息格式。因此,如果应用程序的数据格式发生更改,则更改的应用程序和与之通信的所有其他应用程序之间的所有消息转换器都必须更改。同样,如果将新应用程序添加到解决方案中,则必须从每个现有应用程序到新应用程序创建新的消息转换器,以便交换消息。这种情况造成了一场噩梦,因为必须维护所有消息转换器

                                                                                                                                                                                        While the Mes­sage Trans­lator provides an in­dir­ec­tion between the mes­sage formats used by two com­mu­nic­at­ing ap­plic­a­tions, it is still de­pend­ent on the mes­sage formats used by either ap­plic­a­tion. As a result, if an ap­plic­a­tion's data format changes, all Mes­sage Trans­lat­ors between the chan­ging ap­plic­a­tion and all other ap­plic­a­tions that it com­mu­nic­ates with have to change. Like­wise, if a new ap­plic­a­tion is added to the solu­tion, new Mes­sage Trans­lat­ors have to be cre­ated from each ex­ist­ing ap­plic­a­tion to the new ap­plic­a­tion in order to ex­change mes­sages. This situ­ation cre­ates a night­mare out of having to main­tain all Mes­sage Trans­lat­ors.

                                                                                                                                                                                        我们还需要记住,注入消息流的每个额外转换步骤都会增加延迟并降低消息吞吐量。

                                                                                                                                                                                        We also need to keep in mind that each ad­di­tional trans­form­a­tion step in­jec­ted into a mes­sage flow can in­crease latency and reduce mes­sage through­put.

                                                                                                                                                                                        设计独立于任何特定应用程序的规范数据模型。要求每个应用程序以这种通用格式生成和使用消息。

                                                                                                                                                                                        Design a Ca­non­ical Data Model that is in­de­pend­ent from any spe­cific ap­plic­a­tion. Re­quire each ap­plic­a­tion to pro­duce and con­sume mes­sages in this common format.

                                                                                                                                                                                        图形/08inf23.gif



                                                                                                                                                                                        规范数据模型在应用程序的各个数据格式之间提供了额外的间接级别。如果将新应用程序添加到集成解决方案中,则只需创建规范数据模型之间的转换,无论已参与的应用程序数量如何。

                                                                                                                                                                                        The Ca­non­ical Data Model provides an ad­di­tional level of in­dir­ec­tion between ap­plic­a­tions' in­di­vidual data formats. If a new ap­plic­a­tion is added to the in­teg­ra­tion solu­tion, only trans­form­a­tion between the Ca­non­ical Data Model has to be cre­ated, re­gard­less of the number of ap­plic­a­tions that already par­ti­cip­ate.

                                                                                                                                                                                        如果只有少量应用程序参与集成解决方案,则规范数据模型的使用可能看起来过于复杂。然而,随着应用程序数量的增加,该解决方案很快就会得到回报。如果我们假设每个应用程序向其他应用程序发送消息并从其他应用程序接收消息,那么如果我们直接在应用程序的数据格式之间进行转换,则由两个应用程序组成的解决方案将只需要两个消息转换器,而规范数据模型需要四个消息转换器。 由三个应用程序组成的解决方案需要六个消息转换器无论采用哪种方法。然而,由六个应用程序组成的解决方案在没有规范数据模型的情况下需要 30 (!) 个消息转换器,而在使用规范数据模型时仅需要 12 个消息转换器。

                                                                                                                                                                                        The use of a Ca­non­ical Data Model may seem overly com­plic­ated if only a small number of ap­plic­a­tions par­ti­cip­ate in the in­teg­ra­tion solu­tion. How­ever, the solu­tion quickly pays off as the number of ap­plic­a­tions in­creases. If we assume that each ap­plic­a­tion sends and re­ceives mes­sages to and from each other ap­plic­a­tion, a solu­tion con­sist­ing of two ap­plic­a­tions would re­quire only two Mes­sage Trans­lat­ors if we trans­late between the ap­plic­a­tions' data formats dir­ectly, whereas the Ca­non­ical Data Model re­quires four Mes­sage Trans­lat­ors. A solu­tion con­sist­ing of three ap­plic­a­tions re­quires six Mes­sage Trans­lat­ors with either ap­proach. How­ever, a solu­tion con­sist­ing of six ap­plic­a­tions re­quires 30 (!) Mes­sage Trans­lat­ors without a Ca­non­ical Data Model and only 12 Mes­sage Trans­lat­ors when using a Ca­non­ical Data Model.

                                                                                                                                                                                        如果现有应用程序将来可能被另一个应用程序取代,规范数据模型也可能非常有用。例如,如果许多应用程序与将来可能被新系统取代的遗留系统接口,那么如果构建了规范数据模型的概念,从一个应用程序切换到另一个应用程序的工作量就会大大减少进入原溶液。

                                                                                                                                                                                        The Ca­non­ical Data Model can also be very useful if an ex­ist­ing ap­plic­a­tion is likely to be re­placed by an­other ap­plic­a­tion in the future. For ex­ample, if a number of ap­plic­a­tions in­ter­face with a legacy system that is likely to be re­placed by a new system in the future, the effort of switch­ing from one ap­plic­a­tion to the other is much re­duced if the concept of a Ca­non­ical Data Model is built into the ori­ginal solu­tion.

                                                                                                                                                                                        转型选项

                                                                                                                                                                                        Trans­form­a­tion Op­tions

                                                                                                                                                                                        如何使应用程序符合通用格式?您有三个基本选择:

                                                                                                                                                                                        How do you make ap­plic­a­tions con­form to the common format? You have three basic choices:

                                                                                                                                                                                        1. 更改应用程序的内部数据格式。 这在理论上是可能的,但在复杂的现实场景中不太可能。如果很容易让每个应用程序本机使用相同的数据格式,那么我们最好使用共享数据库而不是消息传递

                                                                                                                                                                                        2. Change the ap­plic­a­tions' in­ternal data format. This may be pos­sible in theory, but it is un­likely in a com­plex, real-life scen­ario. If it was easy to just make each ap­plic­a­tion to nat­ively use the same data format, we would be better off using Shared Data­base in­stead of Mes­saging.

                                                                                                                                                                                        3. 应用程序内实现消息传递映射器。 自定义应用程序可以使用映射器来生成所需的数据格式。

                                                                                                                                                                                        4. Im­ple­ment a Mes­saging Mapper inside the ap­plic­a­tion. Custom ap­plic­a­tions can use a mapper to gen­er­ate the de­sired data format.

                                                                                                                                                                                        5. 使用外部消息转换器 您可以使用外部消息转换器将特定于应用程序的消息格式转换为规范。这可能是转换打包应用程序数据的唯一选择。

                                                                                                                                                                                        6. Use an ex­ternal Mes­sage Trans­lator. You can use an ex­ternal Mes­sage Trans­lator to trans­late from the ap­plic­a­tion-spe­cific mes­sage format into the format spe­cified by the Ca­non­ical Data Model. This may be your only option for trans­form­ing a pack­aged ap­plic­a­tion's data.

                                                                                                                                                                                        是否使用消息传递映射器或外部消息转换器取决于转换的复杂性和应用程序的可维护性。打包的应用程序通常不需要使用消息传递映射器,因为源代码不可用。对于自定义应用程序,选择取决于转换的复杂性。许多集成工具套件提供可视化转换编辑器,可以更快地构建映射规则。然而,如果转换很复杂,这些可视化工具可能会变得笨拙。

                                                                                                                                                                                        Whether to use a Mes­saging Mapper or an ex­ternal Mes­sage Trans­lator de­pends on the com­plex­ity of the trans­form­a­tion and the main­tain­ab­il­ity of the ap­plic­a­tion. Pack­aged ap­plic­a­tions usu­ally elim­in­ate the use of a Mes­saging Mapper be­cause the source code is not avail­able. For custom ap­plic­a­tions the choice de­pends on the com­plex­ity of the trans­form­a­tion. Many in­teg­ra­tion tool suites provide visual trans­form­a­tion ed­it­ors that allow faster con­struc­tion of map­ping rules. How­ever, these visual tools can get un­wieldy if trans­form­a­tions are com­plex.

                                                                                                                                                                                        当使用外部消息转换器时,我们需要区分公共(规范)消息和私有(特定于应用程序)消息。应用程序及其关联的消息转换器之间的消息被视为私有,因为其他应用程序不应使用这些消息。一旦消息翻译器将消息转换为符合规范数据模型的格式,该消息就被认为是公开的并且可以被其他系统使用。

                                                                                                                                                                                        When using an ex­ternal Mes­sage Trans­lator, we need to dis­tin­guish between public (ca­non­ical) mes­sages and private (ap­plic­a­tion-spe­cific) mes­sages. The mes­sages between an ap­plic­a­tion and its as­so­ci­ated Mes­sage Trans­lator are con­sidered private be­cause no other ap­plic­a­tion should be using these mes­sages. Once the Mes­sage Trans­lator trans­forms the mes­sage into a format com­pli­ant with the Ca­non­ical Data Model, the mes­sage is con­sidered public and can be used by other sys­tems.

                                                                                                                                                                                        双重翻译

                                                                                                                                                                                        Double Trans­la­tion

                                                                                                                                                                                        规范数据模型的使用确实会给消息流带来一定量的开销。现在,每条消息都必须经历两个转换步骤,而不是一个:一个从源应用程序的格式转换为通用格式,一个从通用格式转换为目标应用程序的格式。因此,规范数据模型的使用有时被称为双重转换(直接从一个应用程序的格式转换为另一种应用程序的格式称为直接转换))。每个转换步骤都会导致消息流中出现额外的延迟。因此,对于吞吐量非常高的系统,直接翻译可能是唯一的选择。可维护性和性能之间的这种权衡很常见。最好的建议是使用更易于维护的解决方案(即规范数据模型),除非性能要求不允许。一个缓解因素可能是许多翻译是无状态的,因此有助于与并行执行的多个消息翻译器进行负载平衡。

                                                                                                                                                                                        The use of a Ca­non­ical Data Model does in­tro­duce a cer­tain amount of over­head into the mes­sage flow. Each mes­sage now has to un­dergo two trans­la­tion steps in­stead of one: one trans­la­tion from the source ap­plic­a­tion's format into the common format and one from the common format into the target ap­plic­a­tion's format. For this reason, the use of a Ca­non­ical Data Model is some­times re­ferred to as double trans­la­tion (trans­form­ing dir­ectly from one ap­plic­a­tion's format to the other is called direct trans­la­tion). Each trans­la­tion step causes ad­di­tional latency in the flow of mes­sages. There­fore, for very high through­put sys­tems, direct trans­la­tion can be the only choice. This trade-off between main­tain­ab­il­ity and per­form­ance is common. The best advice is to use the more main­tain­able solu­tion (i.e., the Ca­non­ical Data Model) unless per­form­ance re­quire­ments do not allow it. A mit­ig­at­ing factor may be that many trans­la­tions are state­less and there­fore lend them­selves to load bal­an­cing with mul­tiple Mes­sage Trans­lat­ors ex­ecut­ing in par­al­lel.

                                                                                                                                                                                        设计规范数据模型

                                                                                                                                                                                        Design­ing a Ca­non­ical Data Model

                                                                                                                                                                                        设计规范数据模型可能很困难;大多数企业都至少有过一次失败的“企业数据模型”尝试。为了实现平衡的模型,设计人员应努力使统一模型对于所有集成的应用程序同样有效。不幸的是,在实践中,这个理想很难实现。考虑到规范数据模型不必对所有应用程序内使用的完整数据集进行建模,而只需对参与消息传递的部分进行建模(见图),成功设计规范数据模型的机会就会提高。这可以显着降低创建规范数据模型的复杂性。

                                                                                                                                                                                        Design­ing a Ca­non­ical Data Model can be dif­fi­cult; most en­ter­prises have at least one failed "en­ter­prise data model" effort under their belt. To achieve a bal­anced model, de­sign­ers should strive to make the uni­fied model work equally well for all ap­plic­a­tions being in­teg­rated. Un­for­tu­nately, in prac­tice, this ideal is dif­fi­cult to achieve. The chances of suc­cess­fully design­ing a Ca­non­ical Data Model im­prove when con­sid­er­ing that the Ca­non­ical Data Model does not have to model the com­plete set of data used inside all ap­plic­a­tions, but only the por­tion that par­ti­cip­ates in mes­saging (see figure). This can sig­ni­fic­antly reduce the com­plex­ity of cre­at­ing the Ca­non­ical Data Model.

                                                                                                                                                                                        仅对相关数据建模

                                                                                                                                                                                        Mod­el­ing Only the Rel­ev­ant Data

                                                                                                                                                                                        图形/08inf24.gif

                                                                                                                                                                                        使用规范数据模型还可以带来政治优势。使用规范数据模型允许开发人员和业务用户根据公司的业务领域而不是特定的包实现来讨论集成解决方案。例如,打包的应用程序可以以许多不同的内部格式表示客户的共同概念,例如“帐户”、“付款人”和“联系人”。定义规范数据模型通常是解决应用程序之间语义不一致情况的第一步(请参阅 [ Kent ])。

                                                                                                                                                                                        Using a Ca­non­ical Data Model can also have polit­ical ad­vant­ages. Using a Ca­non­ical Data Model allows de­ve­lopers and busi­ness users to dis­cuss the in­teg­ra­tion solu­tion in terms of the com­pany's busi­ness domain, not a spe­cific pack­age im­ple­ment­a­tion. For ex­ample, pack­aged ap­plic­a­tions may rep­res­ent the common concept of a cus­tomer in many dif­fer­ent in­ternal formats, such as "ac­count," "payer," and "con­tact." De­fin­ing a Ca­non­ical Data Model is often the first step to resolv­ing cases of se­mantic dis­son­ance between ap­plic­a­tions (see [Kent]).

                                                                                                                                                                                        数据格式依赖性

                                                                                                                                                                                        Data Format De­pend­en­cies

                                                                                                                                                                                        该模式开头的图显示了每个应用程序之间转换所需的大量转换器,看起来与 Message Broker中显示的图惊人地相似。这提醒我们应用程序之间的依赖关系可以存在于多个级别。消息通道的使用提供了应用程序之间的公共传输层,并消除了应用程序的各个传输协议之间的依赖性。消息路由器可以提供位置无关性,以便发送应用程序不必依赖于接收应用程序的位置。使用通用数据表示(例如 XML)消除了对任何特定于应用程序的数据类型的依赖。最后,规范数据模型解决了对应用程序使用的数据格式和语义的依赖关系。

                                                                                                                                                                                        The figure at the be­gin­ning of this pat­tern show­ing the large number of trans­formers needed to trans­late between each and every ap­plic­a­tion looks sur­pris­ingly sim­ilar to the figure shown in the Mes­sage Broker. This re­minds us that de­pend­en­cies between ap­plic­a­tions can exist at mul­tiple levels. The use of Mes­sage Chan­nels provides a common trans­port layer between ap­plic­a­tions and re­moves de­pend­en­cies between ap­plic­a­tions' in­di­vidual trans­port pro­to­cols. Mes­sage Routers can provide loc­a­tion-in­de­pend­ence so that a send­ing ap­plic­a­tion does not have to depend on the loc­a­tion of the re­ceiv­ing ap­plic­a­tion. The use of a common data rep­res­ent­a­tion such as XML re­moves de­pend­en­cies on any ap­plic­a­tion-spe­cific data types. Fi­nally, the Ca­non­ical Data Model re­solves de­pend­en­cies on the data formats and se­mantics used by the ap­plic­a­tions.

                                                                                                                                                                                        一如既往,唯一不变的就是变化。因此,符合规范数据模型的消息应指定格式指示符

                                                                                                                                                                                        As always, the only con­stant is change. There­fore, mes­sages con­form­ing to the Ca­non­ical Data Model should spe­cify a Format In­dic­ator.

                                                                                                                                                                                        示例: WSDL

                                                                                                                                                                                        Ex­ample: WSDL

                                                                                                                                                                                        当从应用程序访问外部服务时,该服务可能已经指定要使用的规范数据模型。在 XML Web 服务领域,数据格式由 WSDL(Web 服务定义语言;请参阅 [ WSDL 1.1 ])文档指定。WSDL 指定服务可以使用和生成的请求和回复消息的结构。在大多数情况下,WSDL 中指定的数据格式与提供服务的应用程序的内部格式不同。实际上,WSDL 指定了参与对话的双方都可以使用的规范数据模型。双重翻译由消息映射器服务使用者中的消息网关和服务提供者中的EAA ] 。

                                                                                                                                                                                        When ac­cess­ing an ex­ternal ser­vice from your ap­plic­a­tion, the ser­vice may already spe­cify a Ca­non­ical Data Model to be used. In the world of XML Web ser­vices, the data format is spe­cified by a WSDL (Web Ser­vices Defin­i­tion Lan­guage; see [WSDL 1.1]) doc­u­ment. The WSDL spe­cifies the struc­ture of re­quest and reply mes­sages that the ser­vice can con­sume and pro­duce. In most cases, the data format spe­cified in the WSDL is dif­fer­ent than the in­ternal format of the ap­plic­a­tion provid­ing the ser­vice. Ef­fect­ively, the WSDL spe­cifies a Ca­non­ical Data Model to be used by both parties par­ti­cip­at­ing in the con­ver­sa­tion. The double trans­la­tion con­sists of a Mes­saging Mapper or a Mes­saging Gate­way in the ser­vice con­sumer and a Remote Facade [EAA] in the ser­vice pro­vider.



                                                                                                                                                                                        示例: TIBCO ActiveEnterprise

                                                                                                                                                                                        Ex­ample: TIBCO Act­iveEn­ter­prise

                                                                                                                                                                                        许多 EAI 工具套件提供了一整套工具来定义和描述规范数据模型。例如,TIBCO ActiveEnterprise 套件提供了 TIB/Designer,允许用户检查所有常见消息定义。消息定义可以从 XML 模式定义导入或导出到 XML 模式定义。当使用内置可视化工具集实现消息转换器时,该工具向设计人员提供特定于应用程序的数据格式和存储在中央数据格式存储库中的规范数据模型。这简化了两种数据格式之间消息转换器的配置。

                                                                                                                                                                                        Many EAI tool suites provide a com­plete set of tools to define and de­scribe the Ca­non­ical Data Model. For ex­ample, the TIBCO Act­iveEn­ter­prise suite provides the TIB/De­signer that allows the user to in­spect all common mes­sage defin­i­tions. Mes­sage defin­i­tions can be im­por­ted from or ex­por­ted to XML schema defin­i­tions. When im­ple­ment­ing a Mes­sage Trans­lator using a built-in visual tool set, the tool presents the de­signer with both the ap­plic­a­tion-spe­cific data format and the Ca­non­ical Data Model stored in the cent­ral data format re­pos­it­ory. This sim­pli­fies the con­fig­ur­a­tion of the Mes­sage Trans­lator between the two data formats.

                                                                                                                                                                                        TIBCO Designer:维护规范数据模型的 GUI 工具

                                                                                                                                                                                        The TIBCO De­signer: A GUI Tool to Main­tain a Ca­non­ical Data Model

                                                                                                                                                                                        图形/08inf25.gif



                                                                                                                                                                                          贷款经纪人示例

                                                                                                                                                                                          Loan Broker Example

                                                                                                                                                                                          本章演示如何将路由和转换模式组合成更大的解决方案。作为示例场景,我们选择对消费者从多家银行获取贷款报价的过程进行建模。我们冒昧地稍微简化了业务流程,这样我们就可以专注于集成模式的讨论,而不是举办消费者金融服务的讲座。根据我们定义的模式,我们使用不同的编程语言、技术和消息传递模型讨论并为此过程创建三种替代实现。

                                                                                                                                                                                          This chapter demon­strates how to com­pose rout­ing and trans­form­a­tion pat­terns into a larger solu­tion. As an ex­ample scen­ario, we chose to model the pro­cess of a con­sumer ob­tain­ing quotes for a loan from mul­tiple banks. We took the liberty to sim­plify the busi­ness pro­cess a little bit so we can focus on a dis­cus­sion of in­teg­ra­tion pat­terns as op­posed to hold­ing a lec­ture in con­sumer fin­an­cial ser­vices. Based on the pat­terns that we defined, we dis­cuss and create three al­tern­at­ive im­ple­ment­a­tions for this pro­cess, using dif­fer­ent pro­gram­ming lan­guages, tech­no­lo­gies, and mes­saging models.

                                                                                                                                                                                          获取贷款报价

                                                                                                                                                                                          Ob­tain­ing a Loan Quote

                                                                                                                                                                                          在购买贷款时,客户通常会致电多家银行以寻找最优惠利率的交易。每家银行都会询问客户的社会安全号码、贷款金额和所需期限(即还清贷款之前的月数)。然后,每家银行通常会联系信贷机构来调查客户的信用背景。根据所要求的条款和客户的信用记录,银行向消费者回复利率报价(或者庆幸地拒绝)。一旦客户收到所有银行的报价,他或她就可以选择利率最低的最佳报价。

                                                                                                                                                                                          When shop­ping for a loan, a cus­tomer usu­ally calls sev­eral banks to find the deal with the best pos­sible in­terest rate. Each bank asks the cus­tomer for his or her social se­cur­ity number, the amount of the loan, and the de­sired term (i.e., the number of months until the loan has to be paid off). Each bank then in­vest­ig­ates the cus­tomer's credit back­ground, usu­ally by con­tact­ing a credit agency. Based on the re­ques­ted terms and the cus­tomer's credit his­tory, the bank re­sponds with an in­terest rate quote to the con­sumer (or de­clines thank­fully). Once the cus­tomer has re­ceived quotes from all banks, he or she can then select the best offer with the lowest in­terest rate.

                                                                                                                                                                                          消费者与银行交谈以获得贷款报价

                                                                                                                                                                                          A Con­sumer Talk­ing to Banks to Get a Loan Quote

                                                                                                                                                                                          图形/09inf01.gif

                                                                                                                                                                                          由于联系多家银行提出贷款报价请求是一项繁琐的任务,因此贷款经纪人向消费者提供这项服务。贷款经纪人通常不隶属于任何一家银行,但可以接触许多贷款机构。经纪人收集客户数据一次并联系信用机构以获取客户的信用记录。根据信用评分和历史记录,经纪人向最适合满足客户标准的多家银行提出请求。经纪人从银行收集报价结果,并选择最佳报价传回给消费者。

                                                                                                                                                                                          Be­cause con­tact­ing mul­tiple banks with a loan quote re­quest is a te­di­ous task, loan brokers offer this ser­vice to con­sumers. A loan broker is typ­ic­ally not af­fil­i­ated with any one bank but has access to many lend­ing in­sti­tu­tions. The broker gath­ers the cus­tomer data once and con­tacts the credit agency to obtain the cus­tomer's credit his­tory. Based on the credit score and his­tory, the broker presents the re­quest to a number of banks that are best suited to meet the cus­tomer's cri­teria. The broker gath­ers the res­ult­ing quotes from the banks and se­lects the best offer to pass back to the con­sumer.

                                                                                                                                                                                          充当中介的贷款经纪人

                                                                                                                                                                                          A Loan Broker Acting as In­ter­me­di­ary

                                                                                                                                                                                          图形/09inf02.gif

                                                                                                                                                                                          设计消息流

                                                                                                                                                                                          Design­ing the Mes­sage Flow

                                                                                                                                                                                          我们希望使用前面章节中讨论的集成模式来设计一个贷款经纪人系统。为此,我们首先列出贷款经纪人需要执行的各项任务。

                                                                                                                                                                                          We want to design a loan broker system using in­teg­ra­tion pat­terns dis­cussed in the pre­vi­ous chapters. To do this, let's first list the in­di­vidual tasks that the loan broker needs to per­form.

                                                                                                                                                                                          1. 接收消费者的贷款报价请求。

                                                                                                                                                                                          2. Re­ceive the con­sumer's loan quote re­quest.

                                                                                                                                                                                          3. 从信用机构获取信用评分和历史记录。

                                                                                                                                                                                          4. Obtain credit score and his­tory from credit agency.

                                                                                                                                                                                          5. 确定最合适的联系银行。

                                                                                                                                                                                          6. De­term­ine the most ap­pro­pri­ate banks to con­tact.

                                                                                                                                                                                          7. 向每个选定的银行发送请求。

                                                                                                                                                                                          8. Send a re­quest to each se­lec­ted bank.

                                                                                                                                                                                          9. 收集每个选定银行的答复。

                                                                                                                                                                                          10. Col­lect re­sponses from each se­lec­ted bank.

                                                                                                                                                                                          11. 确定最佳响应。

                                                                                                                                                                                          12. De­term­ine the best re­sponse.

                                                                                                                                                                                          13. 将结果返回给消费者。

                                                                                                                                                                                          14. Pass the result back to the con­sumer.

                                                                                                                                                                                          让我们看看哪些模式可以帮助我们设计和实现贷款经纪人。第一步描述代理如何接收传入请求。我们在第 10 章“消息传递端点”中更详细地介绍了这个主题,因此现在,让我们跳过这一步并假设消息以某种方式被代理接收。接下来,经纪人必须检索一些附加信息:客户的信用评分。内容丰富器听起来是这项任务的理想选择。一旦代理获得完整信息,代理必须确定将请求消息路由到的适当银行。我们可以通过另一个内容丰富器来实现这一点计算请求的收件人列表。将请求消息发送给多个接收者并将响应重新组合回单个消息是Scatter-Gather的特点。Scatter -Gather可以使用发布-订阅通道或接收者列表将请求发送到银行。一旦银行回复其利率报价,Scatter-Gather就会使用聚合器将各个利率报价聚合为单个报价,供消费者使用。 如果我们使用这些模式对消息流进行建模,我们将得到以下设计:

                                                                                                                                                                                          Let's see which pat­terns could help us design and im­ple­ment the loan broker. The first step de­scribes how the broker re­ceives the in­com­ing re­quest. We cover this topic in much more detail in Chapter 10, "Mes­saging En­d­points," so for now, let's skip over this step and assume the mes­sage is some­how re­ceived by the broker. Next, the broker has to re­trieve some ad­di­tional in­form­a­tion: the cus­tomer's credit score. A Con­tent En­richer sounds like the ideal choice for this task. Once the broker has the com­plete in­form­a­tion, the broker must de­term­ine the ap­pro­pri­ate banks to route the re­quest mes­sage to. We can ac­com­plish this with an­other Con­tent En­richer that com­putes the list of re­cip­i­ents for the re­quest. Send­ing a re­quest mes­sage to mul­tiple re­cip­i­ents and re­com­bin­ing the re­sponses back into a single mes­sage is the spe­cialty of the Scat­ter-Gather. The Scat­ter-Gather can use a Pub­lish-Sub­scribe Chan­nel or a Re­cip­i­ent List to send the re­quest to the banks. Once the banks reply with their rate quotes, the Scat­ter-Gather ag­greg­ates the in­di­vidual rate quotes into a single quote for the con­sumer using an Ag­greg­ator. If we model the mes­sage flow using these pat­terns, we arrive at the fol­low­ing design:

                                                                                                                                                                                          简单的贷款经纪人设计

                                                                                                                                                                                          Simple Loan Broker Design

                                                                                                                                                                                          图形/09inf03.gif

                                                                                                                                                                                          我们尚未考虑到每家银行可能会使用略有不同的消息格式来处理贷款请求和响应的可能性。因为我们希望将路由和聚合逻辑与银行的专有格式分开,所以我们需要将消息转换器插入经纪人和银行之间的通信线路中。我们可以使用规范化器将各个响应转换为通用格式:

                                                                                                                                                                                          We have not yet ac­coun­ted for the likely event that each bank may use a slightly dif­fer­ent mes­sage format for the loan re­quest and re­sponse. Be­cause we want to sep­ar­ate the rout­ing and ag­greg­a­tion logic from the banks' pro­pri­et­ary formats, we need to insert Mes­sage Trans­lat­ors into the com­mu­nic­a­tion lines between the broker and the banks. We can use a Nor­mal­izer to trans­late the in­di­vidual re­sponses into a common format:

                                                                                                                                                                                          完整的贷款经纪人设计

                                                                                                                                                                                          Com­plete Loan Broker Design

                                                                                                                                                                                          图形/09inf04.gif

                                                                                                                                                                                          排序:同步与异步

                                                                                                                                                                                          Se­quen­cing: Syn­chron­ous versus Asyn­chron­ous

                                                                                                                                                                                          到目前为止,我们已经描述了消息流以及可用于描述贷款经纪人组件设计的路由和转换模式。我们还没有讨论经纪人操作的时间。我们有两个主要选择:

                                                                                                                                                                                          So far, we have de­scribed the flow of the mes­sages and the rout­ing and trans­form­a­tion pat­terns we can use to de­scribe the design of the loan broker com­pon­ent. We have not yet dis­cussed the timing of the broker op­er­a­tion. We have two primary choices:

                                                                                                                                                                                          • 同步(顺序): 经纪商向一家银行询问报价并等待响应,然后再联系下一家银行。

                                                                                                                                                                                          • Syn­chron­ous (Se­quen­tial): The broker asks one bank for the quote and waits for a re­sponse before con­tact­ing the next bank.

                                                                                                                                                                                          • 异步(并行): 经纪商立即发送所有报价请求并等待答案返回。

                                                                                                                                                                                          • Asyn­chron­ous (Par­al­lel): The broker sends all quote re­quests at once and waits for the an­swers to come back.

                                                                                                                                                                                          我们可以使用 UML 序列图来说明这两个选项。同步选项意味着按顺序处理所有贷款请求(参见下图)。该解决方案的优点是更易于管理,因为我们不必处理任何并发问题或线程。然而,这是一个低效的解决方案,因为我们没有利用每个银行拥有独立处理能力并且可以同时执行请求的事实。结果,消费者可能需要等待很长时间才能得到答复。

                                                                                                                                                                                          We can use UML se­quence dia­grams to il­lus­trate the two op­tions. The syn­chron­ous option im­plies a se­quen­tial pro­cess­ing of all loan re­quests (see the fol­low­ing figure). This solu­tion has the ad­vant­age of being sim­pler to manage be­cause we do not have to deal with any con­cur­rency issues or threads. How­ever, it is an in­ef­fi­cient solu­tion be­cause we do not take ad­vant­age of the fact that each bank pos­sesses in­de­pend­ent pro­cess­ing power and could be ex­ecut­ing re­quests sim­ul­tan­eously. As a result, the con­sumer might have to wait a long time for an answer.

                                                                                                                                                                                          同步、顺序处理贷款请求

                                                                                                                                                                                          Syn­chron­ous, Se­quen­tial Pro­cess­ing of Loan Re­quests

                                                                                                                                                                                          图形/09inf05.gif

                                                                                                                                                                                          异步解决方案立即发出所有请求,以便每个银行都可以开始处理。当银行完成计算后,他们将结果返回给贷款经纪人。该解决方案可以实现更快的响应。例如,如果所有银行花费相似的时间来生成贷款报价,则此解决方案几乎快n倍,其中n是我们正在处理的银行数量。贷款经纪人现在必须能够以任何顺序接受贷款报价回复消息,因为不能保证响应到达的顺序与发出请求的顺序相同。以下序列图说明了此选项。贷款报价请求上的空心箭头表示异步调用。

                                                                                                                                                                                          The asyn­chron­ous solu­tion issues all re­quests right away so that each bank can start pro­cess­ing. As the banks finish the com­pu­ta­tion, they return the res­ults to the loan broker. This solu­tion allows for a much quicker re­sponse. For ex­ample, if all banks take a sim­ilar amount of time to pro­duce a loan quote, this solu­tion is almost n times faster, where n is the number of banks we are deal­ing with. The loan broker now must be able to accept the loan quote reply mes­sages in any order, be­cause there is no guar­an­tee that re­sponses arrive in the same order that re­quests were made. The fol­low­ing se­quence dia­gram il­lus­trates this option. The open ar­row­head on a loan quote re­quest in­dic­ates an asyn­chron­ous in­voc­a­tion.

                                                                                                                                                                                          贷款请求的异步并行处理

                                                                                                                                                                                          Asyn­chron­ous, Par­al­lel Pro­cess­ing of Loan Re­quests

                                                                                                                                                                                          图形/09inf06.gif

                                                                                                                                                                                          通过消息队列使用异步调用的另一个显着优点是能够创建多个服务提供者实例。例如,如果事实证明信用局是一个瓶颈,我们可以决定运行该组件的两个实例。由于贷款经纪人将请求消息发送到队列而不是直接发送到信用局组件,因此只要将响应放回响应通道,哪个组件实例处理该消息并不重要。

                                                                                                                                                                                          An­other sig­ni­fic­ant ad­vant­age of using asyn­chron­ous in­voc­a­tion via a mes­sage queue is the abil­ity to create more than one in­stance of a ser­vice pro­vider. For ex­ample, if it turns out that the credit bureau is a bot­tle­neck, we could decide to run two in­stances of that com­pon­ent. Be­cause the loan broker sends the re­quest mes­sage to a queue in­stead of dir­ectly to the credit bureau com­pon­ent, it does not matter which com­pon­ent in­stance pro­cesses the mes­sage as long as the re­sponse is put back onto the re­sponse chan­nel.

                                                                                                                                                                                          解决:分配与拍卖

                                                                                                                                                                                          Ad­dress­ing: Dis­tri­bu­tion versus Auc­tion

                                                                                                                                                                                          使用Scatter-Gather来获取最佳报价允许我们从两种寻址机制中进行选择:接收者列表发布-订阅通道。该决定主要取决于我们希望对被允许参与特定贷款请求的银行施加多少控制。同样,我们有多种选择:

                                                                                                                                                                                          Using a Scat­ter-Gather to obtain the best quote allows us to choose from two ad­dress­ing mech­an­isms, a Re­cip­i­ent List or a Pub­lish-Sub­scribe Chan­nel. The de­cision primar­ily de­pends on how much con­trol we want to exert over the banks who are al­lowed to par­ti­cip­ate in a spe­cific loan re­quest. Again, we have a number of choices:

                                                                                                                                                                                          • 已修复: 银行列表是硬编码的。每个贷款请求都会发送给同一组银行。

                                                                                                                                                                                          • Fixed: The list of banks is hard-coded. Each loan re­quest goes to the same set of banks.

                                                                                                                                                                                          • 分配: 经纪商维护哪些银行适合特定请求的标准。例如,它不会向专门针对优质客户的银行发送信用记录不良的客户的报价请求。

                                                                                                                                                                                          • Dis­tri­bu­tion: The broker main­tains cri­teria on which banks are a good match for a spe­cific re­quest. For ex­ample, it would not send a quote re­quest for a cus­tomer with a poor credit his­tory to a bank that spe­cial­izes in premier cus­tom­ers.

                                                                                                                                                                                          • 拍卖: 代理使用发布-订阅通道广播请求。 任何有兴趣的银行都可以订阅该频道并根据请求“出价”。银行可以随意认购或退订。每家银行仍然可以采用自己的标准来决定是否提交投标。

                                                                                                                                                                                          • Auc­tion: The broker broad­casts the re­quest using a Pub­lish-Sub­scribe Chan­nel. Any bank that is in­ter­ested is al­lowed to sub­scribe to the chan­nel and "bid" on the re­quest. Banks can sub­scribe or un­sub­scribe at will. Each bank can still apply its own cri­teria on whether to submit a bid.

                                                                                                                                                                                          图形/09inf07.gif

                                                                                                                                                                                          哪个选项最适合我们的场景?与往常一样,不存在简单的“答案是......”,但选择是由业务和技术偏好和约束驱动的。第一个选项很简单,让经纪商控制银行列表。然而,如果新银行频繁进出,该解决方案可能会带来管理负担。此外,银行可能不乐意收到一堆不相关的请求,因为每个请求都会给银行带来一定的内部成本。此外,为了保持这种方法的简单性,聚合策略更有可能要求所有银行提交响应。银行可能希望保留拒绝投标的权利。

                                                                                                                                                                                          Which option is best for our scen­ario? As always, there is no simple "and the answer is...," but the choice is driven by both busi­ness and tech­nical pref­er­ences and con­straints. The first option is simple and gives the broker con­trol over the list of banks. How­ever, if new banks come and go fre­quently, the solu­tion can result in an ad­min­is­trat­ive burden. Also, the bank may not be happy to be re­ceiv­ing a bunch of ir­rel­ev­ant re­quests, be­cause each re­quest incurs a cer­tain in­ternal cost for the bank. Also, to main­tain the sim­pli­city of this ap­proach, the ag­greg­a­tion strategy is more likely to re­quire all banks to submit a re­sponse. Banks may want to re­serve the right to with­hold from the bid.

                                                                                                                                                                                          分配方法(使用接收者列表)使经纪人可以更好地控制每个贷款请求涉及哪家银行。这使得代理可以通过减少请求数量来提高效率。它还允许经纪人根据业务关系优先选择某些银行。不利的一面是,它需要在贷款经纪人组件内实现和维护额外的业务逻辑。分发和固定方法都需要为每个参与者提供单独的消息通道,以便控制消息流。

                                                                                                                                                                                          A dis­tri­bu­tion ap­proach (using a Re­cip­i­ent List) gives the broker more con­trol over which bank to in­volve in each loan re­quest. This allows the broker to be more ef­fi­cient by re­du­cing the number of re­quests. It also allows the broker to prefer cer­tain banks based on the busi­ness re­la­tion­ship. On the down­side, it re­quires ad­di­tional busi­ness logic to be im­ple­men­ted and main­tained inside the loan broker com­pon­ent. Both the dis­tri­bu­tion and the fixed ap­proach re­quire a sep­ar­ate Mes­sage Chan­nel for each par­ti­cipant in order to con­trol the mes­sage flow.

                                                                                                                                                                                          使用发布-订阅通道向所有订阅银行广播贷款请求,并让每个银行确定要服务的请求。每个银行都可以使用消息过滤器或实施选择性消费者来过滤掉不需要的贷款请求。这种方法使得贷款经纪人在添加或删除银行的情况下几乎不需要维护,但需要银行方面做更多的工作。该解决方案仅需要一个消息通道,但必须将其实现为发布-订阅通道。许多高效的发布-订阅方案使用通常不跨广域网或 Internet 进行路由的 IP 多播。其他实现通过使用点对点和接收者列表来模拟发布-订阅通道。这种方法保留了发布-订阅通道的简单语义,但在使用通道和网络带宽方面效率较低。有关路由和发布-订阅加过滤之间的其他权衡,请参阅消息过滤器的描述。

                                                                                                                                                                                          Using a Pub­lish-Sub­scribe Chan­nel broad­casts a loan re­quest to all sub­scrib­ing banks and lets each bank de­term­ine which re­quests to ser­vice. Each bank can use a Mes­sage Filter or im­ple­ment a Se­lect­ive Con­sumer to filter out un­desir­able loan re­quests. This ap­proach renders the loan broker pretty much main­ten­ance-free in cases of adding or re­mov­ing banks, but it re­quires more work on the side of the banks. This solu­tion re­quires only a single Mes­sage Chan­nel , but it has to be im­ple­men­ted as a Pub­lish-Sub­scribe Chan­nel. Many ef­fi­cient pub­lish-sub­scribe schemes use IP Mul­tic­ast that typ­ic­ally does not route across wide-area net­works or the In­ter­net. Other im­ple­ment­a­tions emu­late a Pub­lish-Sub­scribe Chan­nel by using an array of Point-to-Point Chan­nels and a Re­cip­i­ent List. This ap­proach pre­serves the simple se­mantics of a Pub­lish-Sub­scribe Chan­nel but is less ef­fi­cient in its use of chan­nels and net­work band­width. For ad­di­tional trade-offs between rout­ing and pub­lish-sub­scribe plus fil­ter­ing, see the de­scrip­tion of the Mes­sage Filter.

                                                                                                                                                                                          聚合策略:多通道与单通道

                                                                                                                                                                                          Ag­greg­at­ing Strategies: Mul­tiple Chan­nels versus Single Chan­nel

                                                                                                                                                                                          当收到银行的贷款报价时,我们有类似的设计选择。我们可以让所有银行将其答复提交到单个答复渠道,或者我们可以为每家银行设立单独的答复渠道。使用单一通道减少了为每个参与银行设置单独通道的维护负担,但要求每个银行的回复消息包含一个用于标识发出报价的银行的字段。如果我们使用单个响应通道,聚合器可能不知道需要多少响应消息,除非接收者列表将此信息传递给聚合器(我们称之为初始化聚合器) 。如果我们使用拍卖方式发布-订阅通道,贷款经纪人不知道可能的响应数量,因此聚合器必须采用不依赖于参与者总数的完整性条件。例如,聚合器可以但如果暂时只有两家银行参与,即便如此也会存在风险。在这种情况下,聚合器可能会

                                                                                                                                                                                          When re­ceiv­ing loan quotes from the bank, we have sim­ilar design choices. We can have all banks submit their re­sponses to a single re­sponse chan­nel, or we can have a sep­ar­ate re­sponse chan­nel for each bank. Using a single chan­nel re­duces the main­ten­ance burden of set­ting up a sep­ar­ate chan­nel for each par­ti­cip­at­ing bank but re­quires each bank's reply mes­sage to in­clude a field identi­fy­ing the bank who issued the quote. If we use a single re­sponse chan­nel, the Ag­greg­ator may not know how many re­sponse mes­sages to expect unless the Re­cip­i­ent List passes this in­form­a­tion to the Ag­greg­ator (we call this an ini­tial­ized Ag­greg­ator). If we use an auc­tion-style Pub­lish-Sub­scribe Chan­nel , the number of pos­sible re­sponses is un­known to the loan broker, so the Ag­greg­ator has to employ a com­plete­ness con­di­tion that does not depend on the total number of par­ti­cipants. For ex­ample, the Ag­greg­ator could simply wait until it has a min­imum of three re­sponses. But even that would be risky if tem­por­ar­ily only two banks par­ti­cip­ate. In that case, the Ag­greg­ator could time out and report that it re­ceived an in­suf­fi­cient number of re­sponses.

                                                                                                                                                                                          管理并发

                                                                                                                                                                                          Man­aging Con­cur­rency

                                                                                                                                                                                          诸如贷款经纪人之类的服务应该能够处理想要同时使用该服务的多个客户。例如,如果我们将贷款经纪人功能公开为 Web 服务或将其连接到公共网站,我们实际上无法控制客户端的数量,并且可能会收到数百或数千个并发请求。我们可以让贷款经纪人使用两种不同的策略来处理多个并发请求:

                                                                                                                                                                                          A ser­vice such as a loan broker should be able to deal with mul­tiple cli­ents want­ing to use the ser­vice con­cur­rently. For ex­ample, if we expose the loan broker func­tion as a Web ser­vice or con­nect it to a public Web site, we do not really have any con­trol over the number of cli­ents and we may re­ceive hun­dreds or thou­sands of con­cur­rent re­quests. We can enable the loan broker to pro­cess mul­tiple con­cur­rent re­quests using two dif­fer­ent strategies:

                                                                                                                                                                                          • 执行多个实例。

                                                                                                                                                                                          • Ex­ecute mul­tiple in­stances.

                                                                                                                                                                                          • 单个事件驱动实例。

                                                                                                                                                                                          • A single event-driven in­stance.

                                                                                                                                                                                          第一个选项维护贷款经纪人组件的多个并行实例。我们可以为每个传入请求启动一个新实例,或者维护一个活动贷款经纪人进程的“池”,并将传入请求分配给下一个可用进程(使用消息调度程序))。如果没有可用的进程,我们会将请求排队,直到有可用的进程。进程池的优点是我们可以以可预测的方式分配系统资源。例如,我们可以决定最多执行 20 个贷款经纪人实例。相反,如果我们为每个请求启动一个新进程,那么如果并发请求激增,我们可能会很快阻塞机器。此外,维护正在运行的进程池允许我们为多个请求重用现有进程,从而节省进程实例化和初始化的时间。

                                                                                                                                                                                          The first option main­tains mul­tiple par­al­lel in­stances of the loan broker com­pon­ent. We can either start a new in­stance for each in­com­ing re­quest or main­tain a "pool" of active loan broker pro­cesses and assign in­com­ing re­quests to the next avail­able pro­cess (using a Mes­sage Dis­patcher). If no pro­cess is avail­able, we would queue up the re­quests until a pro­cess be­comes avail­able. A pro­cess pool has the ad­vant­age that we can al­loc­ate system re­sources in a pre­dict­able way. For ex­ample, we can decide to ex­ecute a max­imum of 20 loan broker in­stances. In con­trast, if we star­ted a new pro­cess for each re­quest, we could quickly choke the ma­chine if a spike of con­cur­rent re­quests ar­rives. Also, main­tain­ing a pool of run­ning pro­cesses allows us to reuse an ex­ist­ing pro­cess for mul­tiple re­quests, saving time for pro­cess in­stan­ti­ation and ini­tial­iz­a­tion.

                                                                                                                                                                                          由于贷款经纪人所需的大部分处理都是等待外部各方(信用局和银行)的答复,因此运行许多并行进程可能无法很好地利用系统资源。相反,我们可以运行一个流程实例,在传入消息事件到达时对其做出反应。处理单个消息(例如,银行报价)是一项相对简单的任务,因此单个进程可能能够为许多并发请求提供服务。这种方法可以更有效地使用系统资源并简化解决方案的管理,因为我们只需监视单个流程实例。潜在的缺点是可扩展性有限,因为我们被绑定到一个进程。许多大容量应用程序结合使用这两种技术,

                                                                                                                                                                                          Be­cause much of the pro­cess­ing re­quired by the loan broker is to wait for replies from ex­ternal parties (the credit bureau and the banks), run­ning many par­al­lel pro­cesses may not be a good use of system re­sources. In­stead, we can run a single pro­cess in­stance that reacts to in­com­ing mes­sage events as they arrive. Pro­cess­ing an in­di­vidual mes­sage (e.g., a bank quote) is a re­l­at­ively simple task, so that a single pro­cess may be able to ser­vice many con­cur­rent re­quests. This ap­proach uses system re­sources more ef­fi­ciently and sim­pli­fies man­age­ment of the solu­tion, since we have to mon­itor only a single pro­cess in­stance. The po­ten­tial down­side is the lim­ited scalab­il­ity be­cause we are tied to one pro­cess. Many high-volume ap­plic­a­tions use a com­bin­a­tion of the two tech­niques, ex­ecut­ing mul­tiple par­al­lel pro­cesses, each of which can handle mul­tiple re­quests con­cur­rently.

                                                                                                                                                                                          执行多个并发请求需要我们将系统中的每条消息关联到正确的流程实例。例如,对于银行来说,将所有回复消息发送到固定通道可能是最方便的。这意味着回复通道可以包含与不同客户的并发报价请求相关的消息。因此,我们需要为每条消息配备一个关联标识符,以识别银行正在响应哪个客户请求。

                                                                                                                                                                                          Ex­ecut­ing mul­tiple con­cur­rent re­quests re­quires us to as­so­ci­ate each mes­sage in the system to the cor­rect pro­cess in­stance. For ex­ample, it may be most con­veni­ent for a bank to send all reply mes­sages to a fixed chan­nel. This means that the reply chan­nel can con­tain mes­sages re­lated to dif­fer­ent cus­tom­ers' con­cur­rent quote re­quests. There­fore, we need to equip each mes­sage with a Cor­rel­a­tion Iden­ti­fier to identify which cus­tomer re­quest the bank is re­spond­ing to.

                                                                                                                                                                                          三种实现方式

                                                                                                                                                                                          Three Im­ple­ment­a­tions

                                                                                                                                                                                          为了实现贷款经纪人示例,我们需要做出三个主要设计决策:我们必须为请求选择排序方案,为银行选择寻址方案,并定义聚合策略。此外,我们还必须选择一种编程语言和消息传递基础设施。总的来说,这些单独的选项会导致大量潜在的实施选择。我们选择实施三种代表性解决方案,以突出不同实施选项之间的主要权衡。与本书中的所有示例一样,具体技术的选择有些随意,并不表明特定供应商技术的优越性。下表重点介绍了每种解决方案的特点:

                                                                                                                                                                                          In order to im­ple­ment the loan broker ex­ample, we have three main design de­cisions to make: We have to select a se­quen­cing scheme for the re­quests, select an ad­dress­ing scheme for the banks, and define an ag­greg­a­tion strategy. In ad­di­tion, we have to select a pro­gram­ming lan­guage and a mes­saging in­fra­struc­ture. In ag­greg­ate, these in­di­vidual op­tions result in a large number of po­ten­tial im­ple­ment­a­tion choices. We chose to im­ple­ment three rep­res­ent­at­ive solu­tions to high­light the main trade-offs between the dif­fer­ent im­ple­ment­a­tion op­tions. As with all ex­amples in this book, the choice of spe­cific tech­no­lo­gies is some­what ar­bit­rary and does not in­dic­ate the su­peri­or­ity of a spe­cific vendor's tech­no­logy. The fol­low­ing table high­lights the char­ac­ter­ist­ics of each solu­tion:

                                                                                                                                                                                          执行

                                                                                                                                                                                          Im­ple­ment­a­tion

                                                                                                                                                                                          测序

                                                                                                                                                                                          Se­quen­cing

                                                                                                                                                                                          寻址

                                                                                                                                                                                          Ad­dress­ing

                                                                                                                                                                                          聚合

                                                                                                                                                                                          Ag­greg­a­tion

                                                                                                                                                                                          通道类型

                                                                                                                                                                                          Chan­nel Type

                                                                                                                                                                                          产品技术

                                                                                                                                                                                          Product Tech­no­logy

                                                                                                                                                                                          A

                                                                                                                                                                                          A

                                                                                                                                                                                          同步

                                                                                                                                                                                          Syn­chron­ous

                                                                                                                                                                                          分配

                                                                                                                                                                                          Dis­tri­bu­tion

                                                                                                                                                                                          渠道

                                                                                                                                                                                          Chan­nel

                                                                                                                                                                                          网络服务/SOAP

                                                                                                                                                                                          Web Ser­vice/SOAP

                                                                                                                                                                                          Java/阿帕奇轴

                                                                                                                                                                                          Java/Apache Axis

                                                                                                                                                                                          B

                                                                                                                                                                                          异步

                                                                                                                                                                                          Asyn­chron­ous

                                                                                                                                                                                          分配

                                                                                                                                                                                          Dis­tri­bu­tion

                                                                                                                                                                                          相关ID

                                                                                                                                                                                          Cor­rel­a­tion ID

                                                                                                                                                                                          消息队列

                                                                                                                                                                                          Mes­sage Queue

                                                                                                                                                                                          C#/微软MSMQ

                                                                                                                                                                                          C#/Mi­crosoft MSMQ

                                                                                                                                                                                          C

                                                                                                                                                                                          C

                                                                                                                                                                                          异步

                                                                                                                                                                                          Asyn­chron­ous

                                                                                                                                                                                          拍卖

                                                                                                                                                                                          Auc­tion

                                                                                                                                                                                          相关ID

                                                                                                                                                                                          Cor­rel­a­tion ID

                                                                                                                                                                                          发布-订阅

                                                                                                                                                                                          Pub­lish-Sub­scribe

                                                                                                                                                                                          TIBCO 活跃企业

                                                                                                                                                                                          TIBCO Active-En­ter­prise

                                                                                                                                                                                          第一个实现使用用 Java 和 Apache Axis 实现的同步 Web 服务。与每个银行的通信通过单独的 HTTP 通道进行,该通道既充当请求通道又充当回复通道。因此,聚合策略基于各个回复通道,不需要关联。第二种实现使用带有消息队列的异步方法。我们使用 Microsoft 的 MSMQ 来实现它,但使用 JMS 或 IBM WebSphere MQ 的实现可能看起来非常相似。最后一个实现使用拍卖方法并利用 TIBCO 的发布-订阅基础设施和实现流程管理器的TIB/IntegrationManager 工具图案。在选项 B 和 C 中,所有回复消息都到达单个通道,并且实现使用相关标识符将回复消息与客户贷款报价查询相关联。

                                                                                                                                                                                          The first im­ple­ment­a­tion uses syn­chron­ous Web ser­vices im­ple­men­ted in Java and Apache Axis. The com­mu­nic­a­tion with each bank occurs over a sep­ar­ate HTTP chan­nel, which serves as both a re­quest and reply chan­nel. There­fore, the ag­greg­a­tion strategy is based on in­di­vidual reply chan­nels and does not re­quire cor­rel­a­tion. The second im­ple­ment­a­tion uses an asyn­chron­ous ap­proach with mes­sage queues. We im­ple­ment it using Mi­crosoft's MSMQ, but an im­ple­ment­a­tion using JMS or IBM Web­Sphere MQ could look very sim­ilar. The last im­ple­ment­a­tion uses an Auc­tion ap­proach and lever­ages TIBCO's pub­lish-sub­scribe in­fra­struc­ture and the TIB/In­teg­ra­tion­Man­ager tool that im­ple­ments the Pro­cess Man­ager pat­tern. In option B and C, all reply mes­sages arrive on a single chan­nel and the im­ple­ment­a­tions use Cor­rel­a­tion Iden­ti­fi­ers to as­so­ci­ate reply mes­sages to cus­tomer loan quote in­quir­ies.

                                                                                                                                                                                            使用Web服务同步实现

                                                                                                                                                                                            Synchronous Implementation Using Web Services

                                                                                                                                                                                            作者:康拉德·F·德克鲁兹

                                                                                                                                                                                            by Conrad F. D'Cruz

                                                                                                                                                                                            本节描述使用 Java 和 XML Web 服务实现贷款经纪人示例。我们使用开源 Apache Axis 工具包来为我们处理 Web 服务机制。我们不希望这成为 Java 开发中的一个练习,因此我们选择这个工具集来抽象实现同步 Web 服务接口的复杂性。相反,本节的讨论重点是我们在设计同步消息传递解决方案时做出的设计决策。

                                                                                                                                                                                            This sec­tion de­scribes the im­ple­ment­a­tion of the loan broker ex­ample using Java and XML Web ser­vices. We use the open source Apache Axis toolkit to take care of the Web ser­vices mech­an­ics for us. We do not want this to be an ex­er­cise in Java de­vel­op­ment, so we chose this tool set to ab­stract the com­plex­it­ies of im­ple­ment­ing a syn­chron­ous Web ser­vice in­ter­face. In­stead, the dis­cus­sion in this sec­tion fo­cuses on the design de­cisions that we make in design­ing a syn­chron­ous mes­saging solu­tion.

                                                                                                                                                                                            解决方案架构

                                                                                                                                                                                            Solu­tion Ar­chi­tec­ture

                                                                                                                                                                                            Web 服务解决方案架构

                                                                                                                                                                                            Web Ser­vices Solu­tion Ar­chi­tec­ture

                                                                                                                                                                                            图形/09inf08.gif

                                                                                                                                                                                            该图显示了贷款经纪人示例的同步预测 Web 服务实现的总体体系结构。贷款经纪人和解决方案的其余部分之间有七个重要的接口。如前所述,SOAP over HTTP 用于在每对参与者之间进行通信。

                                                                                                                                                                                            This figure shows the over­all ar­chi­tec­ture of the syn­chron­ous pre­dict­ive Web ser­vice im­ple­ment­a­tion of the loan broker ex­ample. There are seven sig­ni­fic­ant in­ter­faces between the loan broker and the rest of the solu­tion. As in­dic­ated, SOAP over HTTP is used to com­mu­nic­ate between each pair of par­ti­cipants.

                                                                                                                                                                                            第一个接口是贷款经纪人的入口点,客户端应用程序使用它来传递包含贷款申请信息的消息。客户端通过相同的接口接收从贷款经纪人返回的查询结果。尽管 Axis 服务器未在此图中显示,但它接收来自客户端的消息并实现Service Activator

                                                                                                                                                                                            The first in­ter­face is the entry point into the loan broker that the client ap­plic­a­tion uses to pass in the mes­sage con­tain­ing the loan ap­plic­a­tion in­form­a­tion. The client re­ceives the res­ults of the query back from the loan broker via the same in­ter­face. Al­though the Axis server is not shown in this dia­gram, it re­ceives the Mes­sage from the client and im­ple­ments the Ser­vice Ac­tiv­ator.

                                                                                                                                                                                            第二个接口是贷款经纪人和信贷机构之间的接口。信贷机构是一个外部机构,为贷款经纪人提供 Web 服务接口,以获取银行所需的其他客户数据。贷款经纪人使用来自信贷机构的数据来丰富传入的请求,从而实现内容丰富器

                                                                                                                                                                                            The second in­ter­face is between the loan broker and the credit agency. The credit agency is an ex­ternal bureau and provides a Web ser­vice in­ter­face for the loan broker to ac­quire ad­di­tional cus­tomer data that is re­quired by the banks. The loan broker en­riches the in­com­ing re­quest with the data from the credit agency, im­ple­ment­ing a Con­tent En­richer.

                                                                                                                                                                                            接下来的五个接口位于贷款经纪人和五家银行之间。每个接口都用于获取该特定银行的贷款利率报价。每个银行接口都提供相同的功能(获取报价),但可以为关联的 SOAP 消息指定不同的格式。因此,贷款经纪人在询问银行之前必须将报价请求消息翻译成每个特定银行所要求的格式。

                                                                                                                                                                                            The next five in­ter­faces are between the loan broker and each of the five banks. Each in­ter­face is used to obtain the rate quote for a loan from that par­tic­u­lar bank. Each bank in­ter­face provides the same func­tion­al­ity (obtain a quote) but may spe­cify a dif­fer­ent format for the as­so­ci­ated SOAP mes­sages. There­fore, the loan broker has to trans­late the quote re­quest mes­sage to the format re­quired by each par­tic­u­lar bank before query­ing the bank.

                                                                                                                                                                                            贷款经纪人使用预测路由直接联系每家银行;也就是说,参与报价收集的银行集合在被要求报价之前就已经知道。在此阶段,贷款经纪人应用程序使用接收者列表模式将请求发送到选定的银行。由于每家银行可以对请求使用不同的格式,因此贷款经纪人需要使用一组消息转换器将请求转换为每家银行所需的格式。

                                                                                                                                                                                            The loan broker con­tacts each bank dir­ectly using pre­dict­ive rout­ing; that is, the set of banks that par­ti­cip­ate in the quote gath­er­ing is known right before they are called for a quote. In this stage the loan broker ap­plic­a­tion sends the re­quest to the se­lec­ted banks using the Re­cip­i­ent List pat­tern. Be­cause each bank can use a dif­fer­ent format for the re­quest, the loan broker needs to use an array of Mes­sage Trans­lat­ors to con­vert the re­quest into the format re­quired by each bank.

                                                                                                                                                                                            同样,每条回复消息都必须使用Normalizer 转换为通用格式,以便所有回复都可以聚合到单个最佳报价中。请求消息和响应消息同步传递;也就是说,客户和贷款经纪人将被阻止,直到每家银行做出响应或超时。贷款经纪人将所有回复汇总为单个最佳报价,并将最佳报价回复发送回客户。

                                                                                                                                                                                            Like­wise, each reply mes­sage has to be con­ver­ted to a common format using a Nor­mal­izer , so that all replies can be ag­greg­ated into the single best quote. The re­quest and re­sponse mes­sages are passed syn­chron­ously; that is, the client and loan broker are blocked until each bank re­sponds or times out. The loan broker ag­greg­ates all re­sponses into the single best quote and sends the best quote reply back to the client.

                                                                                                                                                                                            同步消息传递在某些需要简单解决方案的问题领域非常有用。通过使用同步消息传递,我们不必担心处理异步事件、线程安全或支持它们所需的基础设施。客户端调用贷款经纪人 Web 服务,然后等待服务器的响应。该解决方案中有多个组件,每个组件都会同步调用下一个组件并等待响应。

                                                                                                                                                                                            Syn­chron­ous mes­saging is useful in some prob­lem do­mains where a simple solu­tion is ne­ces­sit­ated. By using syn­chron­ous mes­saging, we do not have to worry about hand­ling asyn­chron­ous events, thread safety, or the in­fra­struc­ture needed to sup­port them. The client in­vokes the loan broker Web ser­vice and then waits for the re­sponse from the server. There are sev­eral com­pon­ents in this solu­tion, and each com­pon­ent makes a syn­chron­ous call to the next com­pon­ent and waits for the re­sponse.

                                                                                                                                                                                            Web 服务设计注意事项

                                                                                                                                                                                            Web Ser­vices Design Con­sid­er­a­tions

                                                                                                                                                                                            XML Web 服务依赖于简单对象访问协议 (SOAP)。SOAP 规范已提交给 W3C,并定义了一个基于 XML 的协议,用于在分散式和分布式系统之间交换消息。有关 SOAP 的更多详细信息,请参阅万维网联盟网站( www.w3.org/TR/SOAP )上的文档。

                                                                                                                                                                                            XML Web ser­vices rely on the Simple Object Access Pro­tocol (SOAP). The SOAP spe­cific­a­tion was sub­mit­ted to the W3C and defines an XML-based pro­tocol for the pur­pose of ex­chan­ging mes­sages between de­cent­ral­ized and dis­trib­uted sys­tems. For more de­tails on SOAP, please refer to the doc­u­ments at the World Wide Web Con­sor­tium Web site (www.w3.org/TR/SOAP).

                                                                                                                                                                                            不幸的是, SOAP 中的S不再有效。我们开玩笑地假设 SOAP 被重命名为“复杂远程访问协议”(您可以找出缩写词)。说真的,设计一个健壮的 Web 服务接口需要我们深入研究许多术语和相关的设计权衡。虽然本书不是对 Web 服务的介绍,但我们认为简要讨论以下设计注意事项很重要:

                                                                                                                                                                                            Un­for­tu­nately, the S in SOAP is no longer valid. We have jok­ingly pos­tu­lated that SOAP be re­named to Com­plex Remote Access Pro­to­co­lyou figure out the ac­ronym. Ser­i­ously, design­ing a robust Web ser­vices in­ter­face re­quires us to dive into a number of ter­min­o­lo­gies and as­so­ci­ated design trade-offs. While this book is not an in­tro­duc­tion to Web ser­vices, we feel it is im­port­ant to briefly dis­cuss the fol­low­ing design con­sid­er­a­tions:

                                                                                                                                                                                            • 传输协议

                                                                                                                                                                                            • Trans­port pro­tocol

                                                                                                                                                                                            • 异步消息传递与同步消息传递

                                                                                                                                                                                            • Asyn­chron­ous mes­saging versus syn­chron­ous mes­saging

                                                                                                                                                                                            • 编码风格(SOAP 编码与文档/文字)

                                                                                                                                                                                            • En­cod­ing style (SOAP en­cod­ing vs. doc/lit­eral)

                                                                                                                                                                                            • 绑定样式(RPC 与文档样式)

                                                                                                                                                                                            • Bind­ing style (RPC vs. doc­u­ment-style)

                                                                                                                                                                                            • 可靠性和安全性

                                                                                                                                                                                            • Re­li­ab­il­ity and se­cur­ity

                                                                                                                                                                                            传输协议

                                                                                                                                                                                            创建 SOAP 规范的目的是允许应用程序以技术中立的方式通过网络对服务进行同步 RPC 式调用。SOAP 规范实际上为 Web 服务的每次调用定义了单独的请求和响应消息(如描述服务的 WSDL 文档中所定义)。尽管 SOAP 是在开发时考虑到消息传递的,但绝大多数 Web 服务应用程序都使用 HTTP 作为传输协议。HTTP 是一个自然的选择,因为它是 Web 上最常用的协议,并且可以穿越防火墙。然而,HTTP 是超文本传输​​协议,它的设计目的是允许使用 Web 浏览器的用户通过 Internet 检索文档,而不是让应用程序相互通信。HTTP 本质上是不可靠的,并且是为同步文档检索而设计的,客户端应用程序使用相同的连接将请求发送到服务器并接收响应。因此,使用 HTTP 的 Web 服务将始终使用同步请求/响应消息传递,因为通过不可靠的通道进行异步消息传递就像将瓶子扔进大海一样有用。

                                                                                                                                                                                            The SOAP spe­cific­a­tion was cre­ated to allow ap­plic­a­tions to make syn­chron­ous RPC-style calls to a ser­vice across the net­work in a tech­no­logy-neut­ral way. The SOAP spe­cific­a­tion ac­tu­ally defines a sep­ar­ate re­quest and re­sponse mes­sage for each in­voc­a­tion to a Web ser­vice (as defined in the WSDL doc­u­ment de­scrib­ing the ser­vice). Even though SOAP was de­ve­loped with mes­saging in mind, the vast ma­jor­ity of the Web ser­vice ap­plic­a­tions use HTTP as the trans­port pro­tocol. HTTP is a nat­ural choice be­cause it is the most com­monly used pro­tocol on the Web and can sneak through fire­walls. How­ever, HTTP is the Hy­per­Text Trans­fer Pro­to­colit was de­signed to allow users with Web browsers to re­trieve doc­u­ments over the In­ter­net, not for ap­plic­a­tions to com­mu­nic­ate with each other. HTTP is in­her­ently un­re­li­able and de­signed for syn­chron­ous doc­u­ment re­triev­althe client ap­plic­a­tion uses the same con­nec­tion for send­ing the re­quest to the server and re­ceiv­ing the re­sponse. There­fore, Web ser­vices that use HTTP will in­vari­ably use syn­chron­ous re­quest/re­sponse mes­saging be­cause asyn­chron­ous mes­saging over an un­re­li­able chan­nel is about as useful as drop­ping a bottle into the ocean.

                                                                                                                                                                                            异步与同步

                                                                                                                                                                                            在 Web 服务的同步实现中,从请求提交到服务器起,客户端连接就保持打开状态。客户端将等待,直到服务器发回响应消息。使用同步RPC通信的优点是客户端应用程序可以在很短的时间内知道Web服务操作的状态(要么收到响应,要么超时)。使用同步消息传递的一个严重限制是服务器可能必须处理大量并发连接,因为每个并发客户端在等待结果时都维护一个打开的连接。这导致服务器应用程序变得越来越复杂。如果对同步服务提供者的调用之一失败,

                                                                                                                                                                                            In a syn­chron­ous im­ple­ment­a­tion of a Web ser­vice, the client con­nec­tion re­mains open from the time the re­quest is sub­mit­ted to the server. The client will wait until the server sends back the re­sponse mes­sage. The ad­vant­age of using the syn­chron­ous RPC com­mu­nic­a­tion is that the client ap­plic­a­tion knows the status of the Web ser­vice op­er­a­tion in a very short time (either it re­ceives a re­sponse or it times out). A ser­i­ous lim­it­a­tion of using syn­chron­ous mes­saging is that the server may have to deal with a large number of con­cur­rent con­nec­tions be­cause each con­cur­rent client main­tains an open con­nec­tion while wait­ing for the res­ults. This causes the server ap­plic­a­tion to become in­creas­ingly com­plex. If one of the in­voc­a­tions to a syn­chron­ous ser­vice pro­vider fails, the server ap­plic­a­tion has to provide the mech­an­ism to trap the fail­ure and re­cover and reroute the pro­cess­ing or flag an error before con­tinu­ing with the other syn­chron­ous in­voc­a­tions.

                                                                                                                                                                                            目前,大多数 Web 服务工具包默认仅支持同步消息传递。然而,一些供应商使用现有的标准和工具(例如异步消息队列框架)模拟了 Web 服务的异步消息传递。一些组织、公司和 Web 服务工作组已经认识到对异步消息传递支持的需求,并正在着手定义标准(例如 WS-ReliableMessaging)。有关 Web 服务标准的最新信息,请参阅万维网联盟网站 http://www.w3.org/参阅第 14 章“结束语”。

                                                                                                                                                                                            At the present time, most Web ser­vices toolkits sup­port only syn­chron­ous mes­saging by de­fault. How­ever, using ex­ist­ing stand­ards and tools such as asyn­chron­ous mes­sage queuing frame­works, some vendors have emu­lated asyn­chron­ous mes­saging for Web ser­vices. Sev­eral or­gan­iz­a­tions, com­pan­ies, and the Web ser­vices work­ing groups have re­cog­nized the need for asyn­chron­ous mes­saging sup­port and are moving toward de­fin­ing stand­ards (e.g., WS-Re­li­ableMes­saging). For the latest on Web ser­vices stand­ards, please refer to the World Wide Web Con­sor­tium Web site at http://www.w3.org/ and see Chapter 14, "Con­clud­ing Re­marks."

                                                                                                                                                                                            编码风格

                                                                                                                                                                                            SOAP 编码的概念引起了相当多的争论和混乱。SOAP 规范定义了一种称为编码样式的模式,由encodingStyle属性指定。此模式可以采用两个值:encoded(通过将属性值设置为http://schemas.xmlsoap.org/soap/encoding/ )和literal(通过指定不同的[或没有]属性值)。此模式确定应​​用程序对象和参数如何在 XML 中“在线”表示。编码(也称为 SOAP 编码)是指 SOAP 规范的第 5 节,它定义了将编程语言类型映射到 XML 的原始机制。Literal(也称为doc/literal )意味着不要这样做。相反,类型信息由外部机制提供,很可能是 WSDL(Web 服务描述语言)文档,该文档使用 XML 模式来准确定义 SOAP 消息中使用的类型。

                                                                                                                                                                                            The notion of SOAP en­cod­ing has led to a fair amount of debate and con­fu­sion. The SOAP spe­cific­a­tion defines a mode called en­cod­ing style spe­cified by the en­cod­ing­Style at­trib­ute. This mode can take on two values: en­coded (by set­ting the at­trib­ute value to http://schemas.xmlsoap.org/soap/en­cod­ing/) and lit­eral (by spe­cify­ing a dif­fer­ent [or no] at­trib­ute value). This mode de­term­ines how ap­plic­a­tion ob­jects and para­met­ers are rep­res­en­ted in the XML "on the wire." En­coded (also re­ferred to as SOAP en­cod­ing) refers to Sec­tion 5 of the SOAP spe­cific­a­tion, which defines a prim­it­ive mech­an­ism for map­ping pro­gram­ming lan­guage types to XML. Lit­eral (also called doc/lit­eral) means don't do that. In­stead, the type in­form­a­tion is provided by an ex­ternal mech­an­ism, more likely than not a WSDL (Web Ser­vices De­scrip­tion Lan­guage) doc­u­ment that uses XML schema to define ex­actly what types are used in the SOAP mes­sage.

                                                                                                                                                                                            这是因为 SOAP 规范是在采用 W3C XML 架构定义 (XSD) 规范之前编写的。因此,最初的 SOAP 规范必须提供一种对类型信息以及为方法调用发送的参数进行编码的方法,因为没有公认的指定方法。真正发挥作用的是复杂的数据类型,例如数组。SOAP 规范的第 5.4.2 节定义了一种用于在 XML 中表示编程语言数组的特定机制,该机制使用特殊的SOAPEnc:Array模式类型。

                                                                                                                                                                                            This came about be­cause the SOAP spe­cific­a­tion was writ­ten prior to the ad­op­tion of the W3C XML Schema Defin­i­tion (XSD) spe­cific­a­tion. Thus, the ori­ginal SOAP spe­cific­a­tion had to provide a way of en­cod­ing type in­form­a­tion along with the para­met­ers being sent for method calls be­cause there was no ac­cep­ted way of spe­cify­ing it. Where this really comes into play is with com­plex data types such as Arrays. Sec­tion 5.4.2 of the SOAP spe­cific­a­tion defines a par­tic­u­lar mech­an­ism for rep­res­ent­ing pro­gram­ming lan­guage arrays in XML that uses a spe­cial SOA­PEnc:Array schema type.

                                                                                                                                                                                            然而,自从采用 XML 模式(参见http://www.w3.org/TR/xmlschema-0/)以来,大多数语言都通过从 XML 指定它们自己的映射(或序列化规则)来不再需要 SOAP 编码编程语言类型的模式。例如,JAX-RPC 规范唯一地指定了 Java 类型如何映射到 XML 模式元素,反之亦然。这样就无需在 XML 中添加额外的编码信息。因此,SOAP 编码不再受到青睐,并已被文本编码所取代,该编码具有由 XML 模式文档(通常采用 WSDL 文档的形式)在外部指定的映射。

                                                                                                                                                                                            How­ever, since the ad­op­tion of XML schema (see http://www.w3.org/TR/xmls­chema-0/), most lan­guages have rendered the need for SOAP en­cod­ing ob­sol­ete by spe­cify­ing their own map­pings (or seri­al­iz­a­tion rules) from XML schema to the pro­gram­ming lan­guage types. For in­stance, the JAX-RPC spe­cific­a­tion uniquely spe­cifies how Java types are mapped to XML schema ele­ments, and vice versa. This ob­vi­ates the need for extra en­cod­ing in­form­a­tion in the XML. As a result, SOAP en­cod­ing is no longer favored and has been su­per­ceded by lit­eral en­cod­ing with the map­ping spe­cified ex­tern­ally by an XML schema doc­u­ment, usu­ally in the form of a WSDL doc­u­ment.

                                                                                                                                                                                            装订方式

                                                                                                                                                                                            WSDL 规范在其 SOAP 绑定中指定了两种不同的绑定样式。绑定样式属性的值为RPCDocument 。这意味着,如果 WSDL 文档指定了一个将绑定样式属性设置为RPC 的操作,那么接收方必须使用 SOAP 规范第 7 节中的规则来解释该消息。例如,这意味着 SOAP 主体内的 XML 元素(称为包装元素)的名称必须与要调用的相应编程语言操作的名称相同,该元素中的每个消息部分必须与该编程语言操作的参数完全对应(在名称和顺序上),并且必须只返回一个元素(必须命名为XXXResponse ,其中XXX是语言中相应操作的名称),其中只包含一个元素,即操作的返回值。

                                                                                                                                                                                            The WSDL spe­cific­a­tion spe­cifies two dif­fer­ent bind­ing styles in its SOAP bind­ing. The values of the bind­ing style at­trib­ute are RPC and Doc­u­ment. This means that if a WSDL doc­u­ment spe­cifies an op­er­a­tion that has a bind­ing style at­trib­ute set to RPC, then the re­ceiver must in­ter­pret that mes­sage using the rules found in Sec­tion 7 of the SOAP spe­cific­a­tion. This means, for in­stance, that the XML ele­ment inside the SOAP body (called a wrap­per ele­ment) must have a name identical to the name of the cor­res­pond­ing pro­gram­ming-lan­guage op­er­a­tion that is to be in­voked, that each mes­sage part within that ele­ment must cor­res­pond ex­actly (in name and order) to a para­meter of that pro­gram­ming-lan­guage op­er­a­tion, and that there must be only a single ele­ment re­turned (which must be named XXXRe­sponse, where XXX is the name of the cor­res­pond­ing op­er­a­tion in the lan­guage) that con­tains inside it ex­actly one ele­ment, which is the return value of the op­er­a­tion.

                                                                                                                                                                                            文档装订风格要宽松得多。文档绑定样式中的消息必须仅由格式良好的 XML 组成。接收它并如何解释它取决于 SOAP 引擎。尽管如此,许多工具(例如来自 Microsoft 的工具)通常使用文档绑定样式和文字编码来表示 RPC 语义。即使使用文档样式,发送的消息也代表命令消息,其中要调用的操作和要传递的参数在文档中编码。

                                                                                                                                                                                            The Doc­u­ment bind­ing style is much looser. A mes­sage in the Doc­u­ment bind­ing style must simply be made up of well-formed XML. It is up to the SOAP engine that re­ceives it how it will be in­ter­preted. Having said that, many tools (such as those from Mi­crosoft) com­monly use Doc­u­ment bind­ing style and Lit­eral en­cod­ing to rep­res­ent RPC se­mantics. Even though a Doc­u­ment style is used, the mes­sage being sent rep­res­ents a Com­mand Mes­sage , with the op­er­a­tion to be in­voked and the para­met­ers to be passed en­coded in the Doc­u­ment.

                                                                                                                                                                                            可靠性和安全性

                                                                                                                                                                                            第 14 章“结束语”中,Sean Neville 描述了旨在解决 Web 服务的可靠性和安全性问题的不断发展的标准。

                                                                                                                                                                                            In Chapter 14, "Con­clud­ing Re­marks," Sean Neville de­scribes evolving stand­ards that aim to ad­dress the issues of re­li­ab­il­ity and se­cur­ity for Web ser­vices.

                                                                                                                                                                                            我们的解决方案使用这些设计选择的最基本且可能仍然最流行的组合。贷款经纪人实现使用 SOAP over HTTP 进行同步通信,使用默认 SOAP 编码样式和 RPC 绑定对消息进行编码。这使得 Web 服务的行为非常类似于远程过程调用。我们走这条路是为了避免陷入对 Web 服务内部争论的困境(嗯,不会比我们已经做的更多),而是可以专注于将同步 Web 服务实现与其他实现进行对比。

                                                                                                                                                                                            Our solu­tion uses the most basic and prob­ably still most pre­val­ent com­bin­a­tion of these design choices. The loan broker im­ple­ment­a­tion uses SOAP over HTTP with syn­chron­ous com­mu­nic­a­tion, en­cod­ing mes­sages with the de­fault SOAP en­cod­ing style and RPC bind­ing. This makes the Web ser­vice behave very much like a Remote Pro­ced­ure In­voc­a­tion. We went this route so that we do not get stuck de­bat­ing Web ser­vices in­tern­als (well, not any more than we already did) but can focus on con­trast­ing the syn­chron­ous Web ser­vices im­ple­ment­a­tion with the other im­ple­ment­a­tions.

                                                                                                                                                                                            阿帕奇轴

                                                                                                                                                                                            Apache Axis

                                                                                                                                                                                            本节提供了 Axis 架构的简要描述,以帮助阐明我们设计中的要点。有关 Axis 的更多详细信息,请参阅 Apache Axis 网站:http://ws.apache.org/axis

                                                                                                                                                                                            This sec­tion provides a brief de­scrip­tion of the Axis ar­chi­tec­ture to help elu­cid­ate the sig­ni­fic­ant points in our design. For ad­di­tional de­tails on Axis, please refer to the Apache Axis Web site at http://ws.apache.org/axis.

                                                                                                                                                                                            第 3 章“消息系统”将消息端点定义为应用程序用于连接消息通道以发送和接收消息的机制。在我们的贷款经纪人应用程序中,Axis 框架本身代表消息通道,其主要功能是代表用户应用程序处理消息。

                                                                                                                                                                                            Chapter 3, "Mes­saging Sys­tems," defines a Mes­sage En­d­point as the mech­an­ism an ap­plic­a­tion uses to con­nect to a mes­saging chan­nel in order to send and re­ceive mes­sages. In our loan broker ap­plic­a­tion, the Axis frame­work itself rep­res­ents the mes­sage chan­nel, and its main func­tion is to handle the pro­cess­ing of mes­sages on behalf of the user ap­plic­a­tion.

                                                                                                                                                                                            Axis 服务器实现服务激活器模式。第 10 章“消息传送端点”描述了服务激活器如何将消息通道连接到应用程序中的同步服务,以便在接收到消息时调用该服务。

                                                                                                                                                                                            The Axis server im­ple­ments the Ser­vice Ac­tiv­ator pat­tern. Chapter 10, "Mes­saging En­d­points," de­scribes how a Ser­vice Ac­tiv­ator con­nects a Mes­sage Chan­nel to a syn­chron­ous ser­vice in an ap­plic­a­tion so that when a mes­sage is re­ceived, the ser­vice is in­voked.

                                                                                                                                                                                            服务激活器在 Axis 服务器内实现,因此开发人员无需费心处理此功能。因此,应用程序代码仅包含应用程序的业务逻辑,Axis 服务器负责所有消息处理服务。

                                                                                                                                                                                            The Ser­vice Ac­tiv­ator is im­ple­men­ted within the Axis server so that the de­ve­loper does not need to bother with this func­tion­al­ity. There­fore, the ap­plic­a­tion code con­tains only busi­ness logic for the ap­plic­a­tion, and the Axis server takes care of all mes­sage-hand­ling ser­vices.

                                                                                                                                                                                            Axis 的客户端编程模型为客户端应用程序提供组件来调用端点 URL,然后接收来自服务器的响应消息。此应用程序中的贷款经纪人客户端是使用 Axis 客户端编程模型的同步客户端。在服务器内,有一个针对服务器支持的每种传输协议的侦听器。当客户端向端点发送消息时,Axis 框架内的传输侦听器会创建一个消息上下文对象,并将其通过框架中的请求链传递。消息上下文包含从客户端接收的实际消息以及传输客户端添加的关联属性。

                                                                                                                                                                                            The client-pro­gram­ming model of Axis provides the com­pon­ents for the client ap­plic­a­tion to invoke an en­d­point URL and then re­ceive the re­sponse mes­sage from the server. The loan broker client in this ap­plic­a­tion is a syn­chron­ous client that uses this client-pro­gram­ming model of Axis. Within the server there is a listener for each trans­port pro­tocol sup­por­ted by the server. When the client sends a mes­sage to the en­d­point, a trans­port listener within the Axis frame­work cre­ates a mes­sage con­text object and passes it through a re­quest chain in a frame­work. The mes­sage con­text con­tains the actual mes­sage re­ceived from the client along with as­so­ci­ated prop­er­ties added by the trans­port client.

                                                                                                                                                                                            Axis 框架由一系列处理程序组成,这些处理程序根据部署配置以及调用框架的是客户端还是服务器以特定顺序调用。处理程序是消息流子系统的一部分,组合在一起称为链。请求消息由链中的一系列请求处理程序处理。任何响应消息都会通过一系列响应处理程序发送回相应的响应链。

                                                                                                                                                                                            The Axis frame­work is made up of a series of Hand­lers, which are in­voked in a par­tic­u­lar order de­pend­ing on the de­ploy­ment con­fig­ur­a­tion and whether the client or server is in­vok­ing the frame­work. Hand­lers are part of the Mes­sage Flow sub­sys­tem and are grouped to­gether and called Chains. Re­quest mes­sages are pro­cessed by a se­quence of re­quest Hand­lers in a Chain. Any re­sponse mes­sages are sent back on the cor­res­pond­ing re­sponse Chain through a se­quence of re­sponse Hand­lers.

                                                                                                                                                                                            图形/09inf09.gif

                                                                                                                                                                                            上图显示了 Axis 框架内部的高级表示。有关 Axis 架构的详细讨论可以在http://ws.apache.org/axis中找到。

                                                                                                                                                                                            The figure above shows a high-level rep­res­ent­a­tion of the in­tern­als of the Axis frame­work. A de­tailed dis­cus­sion on the ar­chi­tec­ture of Axis can be found at http://ws.apache.org/axis.

                                                                                                                                                                                            Axis 由多个子系统组成,这些子系统协同工作以提供消息通道的功能。 该框架可供客户端和服务器应用程序使用。

                                                                                                                                                                                            Axis con­sists of sev­eral sub­sys­tems that work to­gether to provide the func­tion­al­ity of a Mes­sage Chan­nel. This frame­work is avail­able for use by both the client and server ap­plic­a­tions.

                                                                                                                                                                                            我们的示例的相关 Axis 子系统如下:

                                                                                                                                                                                            The rel­ev­ant Axis sub­sys­tems for our ex­ample are as fol­lows:

                                                                                                                                                                                            • 消息模型子系统定义 SOAP 消息的 XML 语法。

                                                                                                                                                                                            • Mes­sage Model sub­sys­tem defines the XML syntax of the SOAP mes­sages.

                                                                                                                                                                                            • 消息流子系统定义了处理程序和链来传递消息。

                                                                                                                                                                                            • Mes­sage Flow sub­sys­tem defines Hand­lers and Chains to pass the mes­sages.

                                                                                                                                                                                            • 服务子系统定义服务处理程序(SOAP、XML-RPC)。

                                                                                                                                                                                            • Ser­vice sub­sys­tem defines the ser­vice hand­ler (SOAP, XML-RPC).

                                                                                                                                                                                            • 传输子系统提供消息传输的替代方案(例如HTTP、JMS、SMTP)。

                                                                                                                                                                                            • Trans­port sub­sys­tem provides al­tern­at­ives for the trans­port of mes­sages (e.g., HTTP, JMS, SMTP).

                                                                                                                                                                                            • 提供者子系统定义了不同类型的类(例如,java:RPC、EJB、MDB)的提供者。

                                                                                                                                                                                            • Pro­vider sub­sys­tem defines the pro­viders for dif­fer­ent types of classes (e.g., java:RPC, EJB, MDB).

                                                                                                                                                                                            如前所述,开发人员只需专注于创建实现业务逻辑的应用程序,然后即可将其部署在 Axis 服务器中。可通过三种技术将 Java 类部署为 Web 服务并使其可用作端点服务:我们将它们命名如下:

                                                                                                                                                                                            As men­tioned earlier, the de­ve­loper only needs to focus on cre­at­ing an ap­plic­a­tion that im­ple­ments the busi­ness logic, which can then be de­ployed in the Axis server. There are three tech­niques for de­ploy­ing a Java class as a Web ser­vice and making it avail­able as an en­d­point ser­vice; we have named them as fol­lows:

                                                                                                                                                                                            • 自动部署

                                                                                                                                                                                            • Auto­matic de­ploy­ment

                                                                                                                                                                                            • 使用 Web 服务部署描述符

                                                                                                                                                                                            • Using a Web Ser­vices De­ploy­ment Descriptor

                                                                                                                                                                                            • 从现有 WSDL 文档生成代理

                                                                                                                                                                                            • Gen­er­ate prox­ies from an ex­ist­ing WSDL doc­u­ment

                                                                                                                                                                                            第一种也是最简单的方法是将包含业务逻辑的类编写为 Java Web 服务 (JWS) 文件(这是扩展名为*.jws的 Java 源文件)。此类的方法包含业务逻辑。JWS文件不需要编译,只需将源文件复制到服务器上的webapps目录即可立即部署。每个公共方法现在都可以作为 Web 服务进行访问。此 JWS 文件的名称构成 Axis 服务器作为 Web 服务公开的端点的一部分,如以下 URL 所示:

                                                                                                                                                                                            The first and simplest way is to write the class con­tain­ing the busi­ness logic as a Java Web Ser­vice (JWS) file (this is a Java source file with a *.jws ex­ten­sion). The meth­ods of this class con­tain the busi­ness logic. The JWS file does not need to be com­piled and can be in­stantly de­ployed by copy­ing the source file into the webapps dir­ect­ory on the server. Each public method is now ac­cess­ible as a Web ser­vice. The name of this JWS file forms part of the en­d­point that the Axis server ex­poses as a Web ser­vice, as shown in the fol­low­ing URL:

                                                                                                                                                                                            http://主机名:端口号/axis/LoanBroker.jws

                                                                                                                                                                                            http://host­name:port­num­ber/axis/Loan­Broker.jws

                                                                                                                                                                                            Axis 1.1 从服务器内部署的服务自动生成 WSDL 文档。WSDL 是一种 XML 格式,它描述 Web 服务的公共接口(即通过该接口可用的方法)以及服务的位置(即 URL)。WSDL 的自动生成允许其他应用程序检查 Web 服务类提供的远程接口。它还可用于自动生成客户端存根类,将 Web 服务调用封装在常规 Java 类中。这种方法的缺点是开发者无法控制部署参数。

                                                                                                                                                                                            Axis 1.1 auto­mat­ic­ally gen­er­ates the WSDL doc­u­ment from ser­vices de­ployed within the server. WSDL is an XML format that de­scribes the public in­ter­face of a Web ser­vice (i.e., the meth­ods avail­able through the in­ter­face) as well as the loc­a­tion of the ser­vice (i.e., the URL). The auto­matic gen­er­a­tion of WSDL allows other ap­plic­a­tions to in­spect the remote in­ter­face provided by the Web ser­vice class. It can also be used to auto­mat­ic­ally gen­er­ate client stub classes that en­cap­su­late the Web ser­vices call inside a reg­u­lar Java class. The dis­ad­vant­age of this method is that the de­ve­loper cannot con­trol the de­ploy­ment para­met­ers.

                                                                                                                                                                                            第二种技术使用 WSDD(Web 服务部署描述符)部署已编译的类,这允许开发人员控制部署参数,例如类范围。默认情况下,类会部署在请求范围内;也就是说,为收到的每个请求实例化该类的一个新实例。一旦处理完成,实例就会被销毁。如果该类必须在整个会话中持续存在,以便在一段时间内为同一客户端的多个请求提供服务,我们需要在会话范围内定义该类。对于某些应用程序,我们需要所有客户端都访问一个单例类;也就是说,该类必须被实例化并在应用程序处于活动状态的整个持续时间内可用,因此 Web 服务是在应用程序范围内定义的。

                                                                                                                                                                                            The second tech­nique de­ploys a com­piled class using a WSDD (Web Ser­vices De­ploy­ment Descriptor), which allows the de­ve­loper to con­trol the de­ploy­ment para­met­ers, such as the class scope. By de­fault, a class gets de­ployed in the re­quest scope; that is, a new in­stance of the class is in­stan­ti­ated for each re­quest that is re­ceived. Once the pro­cess­ing is fin­ished, the in­stance is des­troyed. If the class has to per­sist for the entire ses­sion to ser­vice mul­tiple re­quests from the same client over a period of time, we need to define the class in the ses­sion scope. For some ap­plic­a­tions, we need all cli­ents to access a singleton class; that is, the class has to be in­stan­ti­ated and made avail­able for the entire dur­a­tion the ap­plic­a­tion is active, and so the Web ser­vice is defined in the ap­plic­a­tion scope.

                                                                                                                                                                                            最后一种技术比前两种技术更复杂,但允许从现有 WSDL 文档生成代理和框架(使用wsdl2java工具)。代理和骨架封装了所有与 SOAP 相关的代码,以便开发人员不必编写任何与 SOAP 相关(或与 Axis 相关)的代码,而是可以将业务逻辑直接插入到生成的骨架的方法体中。

                                                                                                                                                                                            The last tech­nique is more com­plic­ated than the pre­vi­ous two tech­niques but allows the gen­er­a­tion of prox­ies and skel­et­ons from an ex­ist­ing WSDL doc­u­ment (using the wsdl2­java tool). The prox­ies and skel­et­ons en­cap­su­late all SOAP-re­lated code so that the de­ve­loper does not have to write any SOAP-re­lated (or Axis-re­lated) code, but can in­stead insert the busi­ness logic right into the method bodies of the gen­er­ated skel­eton.

                                                                                                                                                                                            我们选择使用自动部署技术和 JWS 文件来实现所有 Web 服务,以便使设计和部署要求尽可能简单。在客户端,手动编写对 Web 服务的调用要容易得多。我们可以使用wsdl2java工具来生成存根,然后由客户端代码调用;然而,我们试图最大程度地减少代码生成量,因为它会使示例解决方案变得困难。

                                                                                                                                                                                            We chose to im­ple­ment all our Web ser­vices using the Auto­matic De­ploy­ment tech­nique, using JWS files, in order to keep the design and de­ploy­ment re­quire­ments as simple as pos­sible. On the client side, it is a lot easier to hand-code the call to the Web ser­vice. We could have used the wsdl2­java tool to gen­er­ate the stubs, which would then be called by the client code; how­ever we tried to min­im­ize the amount of code gen­er­a­tion be­cause it can make it dif­fi­cult to walk through an ex­ample solu­tion.

                                                                                                                                                                                            服务发现

                                                                                                                                                                                            Ser­vice Dis­cov­ery

                                                                                                                                                                                            在继续讨论贷款经纪人应用程序之前,我们需要描述在实施解决方案时遵循的一些常规步骤,以帮助创建易于部署的应用程序。为了调用部署在服务器上的 Web 服务,任何客户端应用程序都需要知道端点 URL。在 Web 服务模型中,应用程序从公共服务注册表中查找它想要访问的 Web 服务的位置。

                                                                                                                                                                                            Before we pro­ceed with a dis­cus­sion of the loan broker ap­plic­a­tion, we need to de­scribe a few gen­eral steps that we follow when im­ple­ment­ing the solu­tion to help create an ap­plic­a­tion that is easy to deploy. In order to invoke a Web ser­vice that is de­ployed on a server, any client ap­plic­a­tion needs to know the en­d­point URL. In the Web ser­vices model, an ap­plic­a­tion looks up the loc­a­tion of a Web ser­vice it would like to access from a common ser­vice re­gistry.

                                                                                                                                                                                            UDDI(通用描述、发现和集成)是此类存储库的标准。对 UDDI 的讨论超出了本节的范围;您可以在http://www.uddi.org获取更多信息。在我们的示例中,我们在应用程序本身中对端点 URL 进行了硬编码。但是,为了轻松部署示例代码,我们为服务器和客户端应用程序创建属性文件。属性文件包含主机名和端口号参数的名称-值对,它们与您的 Axis 服务器安装参数相匹配。这为您在环境中部署贷款经纪人应用程序提供了一定的灵活性。我们提供了一个实用方法,readProps(),在一些 Java 类中。此方法用于读取包含 Axis 服务器部署参数的文件。贷款经纪人应用程序的任何功能方面都不使用readProps ()方法。

                                                                                                                                                                                            UDDI (Uni­ver­sal De­scrip­tion, Dis­cov­ery, and In­teg­ra­tion) is a stand­ard for such a re­pos­it­ory. A dis­cus­sion of UDDI is beyond the scope of this sec­tion; you can get ad­di­tional in­form­a­tion at http://www.uddi.org. In our ex­ample, we have hard-coded the en­d­point URLs in the ap­plic­a­tion itself. How­ever, to make it easy to deploy the ex­ample code, we create prop­er­ties files for both the server and client ap­plic­a­tions. The prop­er­ties file has name-value pairs for the host­name and port number para­met­ers, which match the para­met­ers of your Axis server in­stall­a­tion. This gives you some flex­ib­il­ity in de­ploy­ing the loan broker ap­plic­a­tion in your en­vir­on­ment. We provide a util­ity method, read­Props(), in some of the Java classes. This method is for read­ing files that con­tain the de­ploy­ment para­met­ers of the Axis server. The read­Props() method is not used by any of the func­tional as­pects of the loan broker ap­plic­a­tion.

                                                                                                                                                                                            在任何分布式计算框架中,无论是Java RMI、CORBA还是SOAP Web服务,我们都需要将远程对象上的方法调用的参数定义为原始数据类型或可以序列化到网络和从网络传出的对象。这些参数是从客户端发送到服务器的消息对象的属性。为了使贷款经纪人解决方案保持简单,我们使用 Java String对象将贷款经纪人的响应返回给客户端。如果调用参数是原始数据类型(例如intdouble等),则必须将它们包装在称为类型包装器的预定义对象包装器中(例如Integer 、Double等)。

                                                                                                                                                                                            In any dis­trib­uted com­put­ing frame­work, whether it is Java RMI, CORBA, or SOAP Web ser­vices, we need to define para­met­ers of the method calls on remote ob­jects as prim­it­ive data types or ob­jects that can be seri­al­ized to and from the net­work. These para­met­ers are the prop­er­ties of the mes­sage ob­jects that are sent from the client to the server. To keep the loan broker solu­tion simple, we use a Java String object to return the re­sponse from the loan broker to the client. If the call­ing para­met­ers are prim­it­ive data types (e.g., int, double, etc.), they have to be wrapped in the pre­defined object wrap­pers called the type wrap­pers (e.g., In­teger, Double, etc.).

                                                                                                                                                                                            贷款经纪人申请

                                                                                                                                                                                            The Loan Broker Ap­plic­a­tion

                                                                                                                                                                                            下页上的图显示了贷款经纪人组件的类图。贷款经纪人的核心业务逻辑封装在LoanBrokerWS类中。此类继承自 Axis 框架提供的类,该框架实现Service Activator,以便在 SOAP 请求到达时调用LoanBrokerWS 类中的方法。LoanBrokerWS引用一组网关类,这些网关类实现与外部实体(例如信贷机构和银行)接口的详细信息。此逻辑封装在CreditAgencyGateway 、 LenderGateway和BankQuoteGateway类中。

                                                                                                                                                                                            The figure on the fol­low­ing page shows the class dia­gram for the loan broker com­pon­ent. The core busi­ness logic for the loan broker is en­cap­su­lated inside the Loan­BrokerWS class. This class in­her­its from a class provided by the Axis frame­work that im­ple­ments a Ser­vice Ac­tiv­ator to invoke a method in the Loan­BrokerWS class when a SOAP re­quest ar­rives. The Loan­BrokerWS ref­er­ences a set of gate­way classes that im­ple­ment de­tails of in­ter­fa­cing with ex­ternal en­tit­ies, such as the credit agency and the banks. This logic is en­cap­su­lated inside the classes Cred­it­A­gency­G­ate­way, Lender­Gate­way, and BankQuoteG­ate­way.

                                                                                                                                                                                            贷款经纪人类图

                                                                                                                                                                                            Loan Broker Class Dia­gram

                                                                                                                                                                                            图形/09inf10.gif

                                                                                                                                                                                            贷款经纪人与外界的唯一服务接口是客户端访问贷款经纪人服务的消息端点。无需定义消息传递网关,因为 Axis 框架代表应用程序充当网关。

                                                                                                                                                                                            The only ser­vice in­ter­face from the loan broker to the out­side world is the mes­sage en­d­point for the client to access the loan broker ser­vice. There is no need to define a Mes­saging Gate­way , since the Axis frame­work acts as the gate­way on behalf of the ap­plic­a­tion.

                                                                                                                                                                                            以下序列图说明了同步预测 Web 服务示例的组件之间的交互。贷款经纪人首先调用信用机构网关组件,该组件丰富了提供的最低数据,包括客户的信用评分和信用历史长度。贷款经纪人使用丰富的数据来调用贷方网关。该组件实现了接收者列表,该列表使用提供的所有数据来选择可以为贷款请求提供服务的贷方集。

                                                                                                                                                                                            The fol­low­ing se­quence dia­gram il­lus­trates the in­ter­ac­tion between the com­pon­ents for the syn­chron­ous pre­dict­ive Web ser­vice ex­ample. The loan broker first calls the credit agency gate­way com­pon­ent, which en­riches the min­imum data provided with a credit score and the length of credit his­tory for the cus­tomer. The loan broker uses the en­riched data to call the lender gate­way. This com­pon­ent im­ple­ments the Re­cip­i­ent List that uses all the data provided to choose the set of lenders that can ser­vice the loan re­quest.

                                                                                                                                                                                            然后贷款经纪人致电银行报价网关。该组件通过依次调用每个组来执行预测路由操作。银行组件对现实银行业务的接口进行建模。例如,Bank1类是Bank1WS.jws Web 服务的接口,该服务对银行操作进行建模。当收到贷款请求时,在根据所有参数生成利率报价之前,需要执行一些文书工作。在此示例中,报价由“虚拟”银行 Web 服务生成。

                                                                                                                                                                                            The loan broker then calls the bank quote gate­way. This com­pon­ent per­forms the pre­dict­ive rout­ing op­er­a­tion by call­ing each bank in turn. The bank com­pon­ents model the in­ter­face to a real-world bank­ing op­er­a­tion. For ex­ample, the Bank1 class is the in­ter­face to the Bank1WS.jws Web ser­vice that models the bank­ing op­er­a­tion. When a loan re­quest comes in, there is some amount of cler­ical work per­formed before gen­er­at­ing the rate quote based on all the para­met­ers. In this ex­ample, the rate quote is gen­er­ated by a "dummy" bank­ing Web ser­vice.

                                                                                                                                                                                            银行报价网关汇总来自银行的响应,并从收到的所有报价中选择最佳报价。响应被发送回贷款经纪人,贷款经纪人根据最佳报价格式化数据并将报告返回给客户。

                                                                                                                                                                                            The bank quote gate­way ag­greg­ates the re­sponses from the banks and chooses the best quote from all the quotes re­ceived. The re­sponse is sent back to the loan broker, which formats the data from the best quote and re­turns the report to the client.

                                                                                                                                                                                            贷款经纪人序列图

                                                                                                                                                                                            Loan Broker Se­quence Dia­gram

                                                                                                                                                                                            图形/09inf11.gif

                                                                                                                                                                                            为了使数字易于管理,我们在序列图中仅显示了五个银行中的两个。根据为特定请求生成的接收者列表,贷款经纪人可以联系一家银行或所有五家银行来为客户请求提供服务。

                                                                                                                                                                                            We have shown only two of the five banks in the se­quence dia­gram in the in­terest of keep­ing the figure man­age­able. Based on the Re­cip­i­ent List gen­er­ated for a par­tic­u­lar re­quest, the loan broker could con­tact ex­actly one bank or all five banks to ser­vice the client re­quest.

                                                                                                                                                                                            该图突出显示了对每家银行的报价请求的顺序处理。顺序处理有一些优点,因为我们可以在单个线程中运行应用程序,依次调用每个存储体。应用程序不需要生成线程来请求每个银行的报价,因此我们不必担心并发问题。然而,获得响应报价的总时间很长,因为贷款经纪人必须等待每家银行的响应,然后才能向贷方列表中的下一家银行提交请求。最终的结果是客户提交请求后需要等待很长时间才能得到结果报价。

                                                                                                                                                                                            The dia­gram high­lights the se­quen­tial pro­cess­ing of the quote re­quests to each bank. The se­quen­tial pro­cess­ing has some ad­vant­ages be­cause we can run the ap­plic­a­tion in a single thread, call­ing each bank in turn. The ap­plic­a­tion does not need to spawn threads to re­quest a quote from each bank, and so we don't have to worry about con­cur­rency issues. How­ever, the total time to get the re­sponse quote is high, since the loan broker has to wait for a re­sponse from each bank before sub­mit­ting a re­quest to the next bank in the lenders list. The net result is that the cus­tomer will have to wait a long time to get the result quote back after sub­mit­ting the re­quest.

                                                                                                                                                                                            贷款经纪人应用程序的组成部分

                                                                                                                                                                                            Com­pon­ents of the Loan Broker Ap­plic­a­tion

                                                                                                                                                                                            我们现在开始设计贷款经纪人应用程序的功能方面。如前所述,Axis 框架可以支持客户端和服务器应用程序。这允许远程客户端通过网络访问已发布的端点。服务器应用程序可能还需要像客户端一样运行并访问某些其他 Web 服务端点,无论该端点位于同一服务器还是远程服务器上。我们通过将解决方案的关键组件建模为 Web 服务来演示这一点,尽管它们运行在服务器的同一实例上。

                                                                                                                                                                                            We now start design­ing the func­tional as­pects of the loan broker ap­plic­a­tion. As stated earlier, the Axis frame­work can sup­port both the client and server ap­plic­a­tion. This allows a remote client to access the pub­lished en­d­point across the net­work. A server ap­plic­a­tion may also need to act like a client and access some other Web ser­vice en­d­point, re­gard­less of whether that en­d­point is on the same server or a remote server. We demon­strate this by mod­el­ing the key com­pon­ents of our solu­tion as Web ser­vices, albeit run­ning on the same in­stance of our server.

                                                                                                                                                                                            贷款经纪人解决方案必须实现以下功能:

                                                                                                                                                                                            The loan broker solu­tion has to im­ple­ment the fol­low­ing func­tions:

                                                                                                                                                                                            • 接受客户请求

                                                                                                                                                                                            • Accept Client Re­quests

                                                                                                                                                                                            • 检索信用机构数据

                                                                                                                                                                                            • Re­trieve Credit Agency Data

                                                                                                                                                                                            • 实施信用代理服务

                                                                                                                                                                                            • Im­ple­ment the Credit Agency Ser­vice

                                                                                                                                                                                            • 获取报价

                                                                                                                                                                                            • Obtain Quotes

                                                                                                                                                                                            • 实施银行业务

                                                                                                                                                                                            • Im­ple­ment the Bank­ing Op­er­a­tions

                                                                                                                                                                                            接受客户请求

                                                                                                                                                                                            贷款经纪人在名为LoanBrokerWS.jws的 JWS 文件中实现。它将单个公共方法公开为 Web 服务:GetLoanQuote 。贷款经纪人需要客户提供三项数据才能开始处理贷款请求:充当客户识别号的社会保障号 (SSN)、贷款金额以及贷款期限(以月为单位):

                                                                                                                                                                                            The loan broker is im­ple­men­ted in a JWS file named Loan­BrokerWS.jws. It ex­poses a single public method as a Web ser­vice: GetLoan­Quote. The loan broker needs three pieces of data from the client to start pro­cess­ing a loan re­quest: the social se­cur­ity number (SSN) that acts as the cus­tomer iden­ti­fic­a­tion number, the amount of the loan, and the dur­a­tion of the loan in months:

                                                                                                                                                                                            贷款经纪人WS.jws
                                                                                                                                                                                            公共字符串 getLoanQuote(int ssn,双倍贷款金额,int 贷款期限){
                                                                                                                                                                                            
                                                                                                                                                                                              字符串结果=“”;
                                                                                                                                                                                            
                                                                                                                                                                                              results = results + "客户ssn= " + ssn + " 请求贷款金额= " +
                                                                                                                                                                                                                  贷款金额 + " for " + 贷款期限 + " 月数" + "\n\n";
                                                                                                                                                                                              结果 = 结果 + this.getLoanQuotesWithScores(ssn,loanamount,loanduration);
                                                                                                                                                                                            
                                                                                                                                                                                              返回结果;
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                            public String getLoan­Quote(int ssn, double loanamount, int loan­dur­a­tion) {
                                                                                                                                                                                            
                                                                                                                                                                                              String res­ults = "";
                                                                                                                                                                                            
                                                                                                                                                                                              res­ults = res­ults + "Client with ssn= " + ssn + " re­quests a loan of amount= " +
                                                                                                                                                                                                                  loanamount + " for " + loan­dur­a­tion + " months" + "\n\n";
                                                                                                                                                                                              res­ults = res­ults + this.getLoan­QuotesWith­Scores(ssn,loanamount,loan­dur­a­tion);
                                                                                                                                                                                            
                                                                                                                                                                                              return res­ults;
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            此方法中的唯一步骤是调用方法getLoanQuotesWithScores 。此方法返回一个字符串,该字符串沿着 Axis 框架中的响应链发送回客户端。在链的末端,接收请求的传输侦听器接受来自 Axis 框架内的响应消息,并将其发送回网络上的客户端。

                                                                                                                                                                                            The only step in this method is the call to the method getLoan­QuotesWith­Scores. This method re­turns a string, which is sent back to the client along a re­sponse chain in the Axis frame­work. At the end of the chain, the trans­port listener that re­ceived the re­quest ac­cepts the re­sponse mes­sage from within the Axis frame­work and sends it back to the client on the net­work.

                                                                                                                                                                                            如前所述,由于我们对贷款经纪人组件使用 JWS 文件的自动部署,Axis 会为LoanBrokerWS服务生成 WSDL 文件。接下来显示LoanBrokerWS.jws文件的 WSDL 文件。

                                                                                                                                                                                            As de­scribed earlier, since we use the Auto­matic De­ploy­ment of the JWS file for the loan broker com­pon­ent, Axis gen­er­ates the WSDL file for the Loan­BrokerWS ser­vice. The WSDL file for the Loan­BrokerWS.jws file is shown next.

                                                                                                                                                                                            <wsdl:定义 xmlns:wsdl =“http://schemas.xmlsoap.org/wsdl/”
                                                                                                                                                                                                              xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
                                                                                                                                                                                                              xmlns:xsd="http://www.w3.org/2001/XMLSchema">
                                                                                                                                                                                                <wsdl:message name="getLoanQuoteRequest">
                                                                                                                                                                                                    <wsdl:part name="ssn" type="xsd:int"/>
                                                                                                                                                                                                    <wsdl:part name="loanamount" type="xsd:double"/>
                                                                                                                                                                                                    <wsdl:part name="loanduration" type="xsd:int"/>
                                                                                                                                                                                                </wsdl:消息>
                                                                                                                                                                                                <wsdl:message name="getLoanQuoteResponse">
                                                                                                                                                                                                    <wsdl:part name="getLoanQuoteReturn" type="xsd:string"/>
                                                                                                                                                                                                </wsdl:消息>
                                                                                                                                                                                                <wsdl:portType name="LoanBrokerWS">
                                                                                                                                                                                                    <wsdl:操作名称=“getLoanQuote”parameterOrder=“ssn贷款金额贷款持续时间”>
                                                                                                                                                                                                        <wsdl:输入消息=“intf:getLoanQuoteRequest”
                                                                                                                                                                                                                    名称=“getLoanQuoteRequest”/>
                                                                                                                                                                                                        <wsdl:输出消息=“intf:getLoanQuoteResponse”
                                                                                                                                                                                                                    名称=“getLoanQuoteResponse”/>
                                                                                                                                                                                                    </wsdl:操作>
                                                                                                                                                                                                </wsdl:端口类型>
                                                                                                                                                                                                <wsdl:binding name="LoanBrokerWSSoapBinding" type="intf:LoanBrokerWS">
                                                                                                                                                                                                    <wsdlsoap:绑定样式=“rpc”传输=“http://schemas.xmlsoap.org/soap/http”/>
                                                                                                                                                                                                    <wsdl:操作名称=“getLoanQuote”>
                                                                                                                                                                                                        <wsdlsoap:操作soapAction=""/>
                                                                                                                                                                                                        <wsdl:input name="getLoanQuoteRequest">
                                                                                                                                                                                                            <wsdlsoap:bodyencodingStyle =“http://schemas.xmlsoap.org/soap/encoding/”
                                                                                                                                                                                                                           命名空间=“...”使用=“编码”/>
                                                                                                                                                                                                        </wsdl:输入>
                                                                                                                                                                                                        <wsdl:output name="getLoanQuoteResponse">
                                                                                                                                                                                                            <wsdlsoap:bodyencodingStyle =“http://schemas.xmlsoap.org/soap/encoding/”
                                                                                                                                                                                                                           命名空间=“...”使用=“编码”/>
                                                                                                                                                                                                        </wsdl:输出>
                                                                                                                                                                                                    </wsdl:操作>
                                                                                                                                                                                                </wsdl:绑定>
                                                                                                                                                                                                <wsdl:服务名称=“LoanBrokerWSService”>
                                                                                                                                                                                                    <wsdl:端口绑定 =“intf:LoanBrokerWSSoapBinding”名称 =“LoanBrokerWS”>
                                                                                                                                                                                                        <wsdlsoap:地址位置=“http://192.168.1.25:8080/axis/LoanBrokerWS.jws”/>
                                                                                                                                                                                                    </wsdl:端口>
                                                                                                                                                                                                </wsdl:服务>
                                                                                                                                                                                            </wsdl:定义>
                                                                                                                                                                                            
                                                                                                                                                                                            <wsdl:defin­i­tions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                                                                                                                                                                                                              xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
                                                                                                                                                                                                              xmlns:xsd="http://www.w3.org/2001/XMLS­chema">
                                                                                                                                                                                                <wsdl:mes­sage name="getLoan­Quote­Re­quest">
                                                                                                                                                                                                    <wsdl:part name="ssn" type="xsd:int"/>
                                                                                                                                                                                                    <wsdl:part name="loanamount" type="xsd:double"/>
                                                                                                                                                                                                    <wsdl:part name="loan­dur­a­tion" type="xsd:int"/>
                                                                                                                                                                                                </wsdl:mes­sage>
                                                                                                                                                                                                <wsdl:mes­sage name="getLoan­Quo­teResponse">
                                                                                                                                                                                                    <wsdl:part name="getLoan­QuoteReturn" type="xsd:string"/>
                                                                                                                                                                                                </wsdl:mes­sage>
                                                                                                                                                                                                <wsdl:port­Type name="Loan­BrokerWS">
                                                                                                                                                                                                    <wsdl:op­er­a­tion name="getLoan­Quote" para­met­erOr­der="ssn loanamount loan­dur­a­tion">
                                                                                                                                                                                                        <wsdl:input mes­sage="intf:getLoan­Quote­Re­quest"
                                                                                                                                                                                                                    name="getLoan­Quote­Re­quest"/>
                                                                                                                                                                                                        <wsdl:output mes­sage="intf:getLoan­Quo­teResponse"
                                                                                                                                                                                                                    name="getLoan­Quo­teResponse"/>
                                                                                                                                                                                                    </wsdl:op­er­a­tion>
                                                                                                                                                                                                </wsdl:port­Type>
                                                                                                                                                                                                <wsdl:bind­ing name="Loan­Broker­WS­Soap­Bind­ing" type="intf:Loan­BrokerWS">
                                                                                                                                                                                                    <wsdlsoap:bind­ing style="rpc" trans­port="http://schemas.xmlsoap.org/soap/http"/>
                                                                                                                                                                                                    <wsdl:op­er­a­tion name="getLoan­Quote">
                                                                                                                                                                                                        <wsdlsoap:op­er­a­tion soapAc­tion=""/>
                                                                                                                                                                                                        <wsdl:input name="getLoan­Quote­Re­quest">
                                                                                                                                                                                                            <wsdlsoap:body en­cod­ing­Style="http://schemas.xmlsoap.org/soap/en­cod­ing/"
                                                                                                                                                                                                                           namespace="..." use="en­coded"/>
                                                                                                                                                                                                        </wsdl:input>
                                                                                                                                                                                                        <wsdl:output name="getLoan­Quo­teResponse">
                                                                                                                                                                                                            <wsdlsoap:body en­cod­ing­Style="http://schemas.xmlsoap.org/soap/en­cod­ing/"
                                                                                                                                                                                                                           namespace="..." use="en­coded"/>
                                                                                                                                                                                                        </wsdl:output>
                                                                                                                                                                                                    </wsdl:op­er­a­tion>
                                                                                                                                                                                                </wsdl:bind­ing>
                                                                                                                                                                                                <wsdl:ser­vice name="Loan­Broker­WSSer­vice">
                                                                                                                                                                                                    <wsdl:port bind­ing="intf:Loan­Broker­WS­Soap­Bind­ing" name="Loan­BrokerWS">
                                                                                                                                                                                                        <wsdlsoap:ad­dress loc­a­tion="http://192.168.1.25:8080/axis/Loan­BrokerWS.jws"/>
                                                                                                                                                                                                    </wsdl:port>
                                                                                                                                                                                                </wsdl:ser­vice>
                                                                                                                                                                                            </wsdl:defin­i­tions>
                                                                                                                                                                                            

                                                                                                                                                                                            出于空间考虑,我们压缩了 WSDL 文档以突出显示最重要的元素。文档底部的<wsdl:service>元素定义服务名称 ( LoanBrokerWSService ) 和端点位置。<wsdl:operation>元素定义方法(操作)名称以及客户端访问LoanBrokerWS Web服务所需的参数。<wsdl:message>标签中定义的两条消息定义了getLoanQuote操作的请求和响应消息: getLoanQuoteRequest 和getLoanQuoteResponse使用它们传递到 Web 服务或从 Web 服务接收的适当参数。< wsdlsoap:binding>元素确认我们正在使用本章开头讨论的 RPC 绑定样式,而<wsdlsoap:body>表明我们正在使用 SOAP 编码(而不是doc/literal )

                                                                                                                                                                                            In the in­terest of space, we con­densed the WSDL doc­u­ment to high­light the most sig­ni­fic­ant ele­ments. The <wsdl:ser­vice> ele­ment at the bottom of the doc­u­ment defines the ser­vice name (Loan­Broker­WSSer­vice) and the en­d­point loc­a­tion. The <wsdl:op­er­a­tion> ele­ment defines the method (op­er­a­tion) name along with the para­met­ers re­quired for the client to access the Loan­BrokerWS Web ser­vice. The two mes­sages defined in the <wsdl:mes­sage> tags define the re­quest and the re­sponse mes­sages for the getLoan­Quote op­er­a­tion: getLoan­Quote­Re­quest and getLoan­Quo­teResponse with the ap­pro­pri­ate para­met­ers they pass in to or re­ceive from the Web ser­vice. The <wsdlsoap:bind­ing> ele­ment con­firms that we are using the RPC bind­ing style that we dis­cussed at the be­gin­ning of this chapter, while the <wsdlsoap:body> re­veals that we are using SOAP en­cod­ing (as op­posed to doc/lit­eral).

                                                                                                                                                                                            如果您想从服务器查看 WSDL 文件,可以在浏览器窗口中键入以下 URL,其中主机名端口号替换为服务器安装的相应值:

                                                                                                                                                                                            If you want to see the WSDL file from your server, you can type in the fol­low­ing URL in your browser window where host­name and port­num­ber are re­placed with the ap­pro­pri­ate values of your server in­stall­a­tion:

                                                                                                                                                                                            http://主机名:端口号/axis/LoanBrokerWS.jws?wsdl

                                                                                                                                                                                            http://host­name:port­num­ber/axis/Loan­BrokerWS.jws?wsdl

                                                                                                                                                                                            检索信用机构数据

                                                                                                                                                                                            贷款经纪人的另一个要求是收集客户的额外数据以完成贷款请求申请。LoanBrokerWS的下一阶段实现Content Enricher ,如下所示。

                                                                                                                                                                                            An­other re­quire­ment for the loan broker is to gather ad­di­tional data on the cus­tomer to com­plete the loan re­quest ap­plic­a­tion. The next stage of the Loan­BrokerWS im­ple­ments the Con­tent En­richer , as shown below.

                                                                                                                                                                                            贷款经纪人WS.jws
                                                                                                                                                                                            私有字符串 getLoanQuotesWithScores
                                                                                                                                                                                                          (int de_ssn, double de_loanamount, int de_duration) {
                                                                                                                                                                                              字符串 qws_结果 =
                                                                                                                                                                                                     “客户的附加数据:信用评分和信用记录长度\n”;
                                                                                                                                                                                              int ssn = de_ssn;
                                                                                                                                                                                              双倍贷款金额 = de_loanamount;
                                                                                                                                                                                              int 贷款期限 = de_duration;
                                                                                                                                                                                              int 信用分数 = 0;
                                                                                                                                                                                              int 信用历史长度 = 0;
                                                                                                                                                                                            
                                                                                                                                                                                              CreditProfile 信用配置文件 = CreditAgencyGateway.getCustomerCreditProfile(ssn);
                                                                                                                                                                                            
                                                                                                                                                                                              Credit_score = Creditprofile.getCreditScore();
                                                                                                                                                                                              Credit_history_length = Creditprofile.getCreditHistoryLength();
                                                                                                                                                                                            
                                                                                                                                                                                              qws_results = qws_results + "信用评分= " + Credit_score +
                                                                                                                                                                                                            " 信用记录长度= " + Credit_history_length;
                                                                                                                                                                                              qws_results = qws_results + "\n\n";
                                                                                                                                                                                              qws_results = qws_results + "所有回复银行的最佳报价详情为
                                                                                                                                                                                                如下所示:\n\n";
                                                                                                                                                                                            
                                                                                                                                                                                              qws_results = qws_results + getResultsFromLoanClearingHouse
                                                                                                                                                                                                            (ssn,贷款金额,贷款期限,信用历史长度,信用分数);
                                                                                                                                                                                            
                                                                                                                                                                                              qws_results = qws_results + "\n\n";
                                                                                                                                                                                              返回 qws_结果;
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                            private String getLoan­QuotesWith­Scores
                                                                                                                                                                                                          (int de_ssn, double de_loanamount, int de_­dur­a­tion) {
                                                                                                                                                                                              String qws_res­ults =
                                                                                                                                                                                                     "Ad­di­tional data for cus­tomer: credit score and length of credit his­tory\n";
                                                                                                                                                                                              int ssn = de_ssn;
                                                                                                                                                                                              double loanamount = de_loanamount;
                                                                                                                                                                                              int loan­dur­a­tion = de_­dur­a­tion;
                                                                                                                                                                                              int cred­it_score = 0;
                                                                                                                                                                                              int cred­it_his­tory_length = 0;
                                                                                                                                                                                            
                                                                                                                                                                                              Cred­it­Pro­file cred­it­pro­file = Cred­it­A­gency­G­ate­way.get­Cus­tomer­Cred­it­Pro­file(ssn);
                                                                                                                                                                                            
                                                                                                                                                                                              cred­it_score = cred­it­pro­file.getCred­itScore();
                                                                                                                                                                                              cred­it_his­tory_length = cred­it­pro­file.getCreditH­is­toryLength();
                                                                                                                                                                                            
                                                                                                                                                                                              qws_res­ults = qws_res­ults + "Credit Score= " + cred­it_score +
                                                                                                                                                                                                            " Credit His­tory Length= " + cred­it_his­tory_length;
                                                                                                                                                                                              qws_res­ults = qws_res­ults + "\n\n";
                                                                                                                                                                                              qws_res­ults = qws_res­ults + "The de­tails of the best quote from all banks that re­spon­ded are
                                                                                                                                                                                                shown below: \n\n";
                                                                                                                                                                                            
                                                                                                                                                                                              qws_res­ults = qws_res­ults + getRes­ults­From­Loan­Clear­ing­House
                                                                                                                                                                                                            (ssn,loanamount,loan­dur­a­tion,cred­it_his­tory_length,cred­it_score);
                                                                                                                                                                                            
                                                                                                                                                                                              qws_res­ults = qws_res­ults + "\n\n";
                                                                                                                                                                                              return qws_res­ults;
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            我们现在设计信贷代理业务,这是贷款经纪人应用程序的下一个逻辑领域。为了将 SOAP 代码排除在贷款经纪人应用程序之外,并最大限度地减少贷款经纪人和信贷机构之间的依赖关系,我们使用网关模式 [EAA],它有两个关键优点:首先,它抽象了贷款经纪人和信贷机构之间的技术细节。来自应用程序的通信。其次,如果我们选择将网关接口与网关实现分离,我们可以用Service Stub [ EAA ] 替换实际的外部服务进行测试。

                                                                                                                                                                                            We now design the credit agency op­er­a­tions, which is the next lo­gical area of the loan broker ap­plic­a­tion. In order to keep the SOAP code out of the loan broker ap­plic­a­tion and min­im­ize the de­pend­en­cies between the loan broker and the credit agency, we use the Gate­way pat­tern [EAA], which has two key ad­vant­ages: First, it ab­stracts the tech­nical de­tails of the com­mu­nic­a­tion from the ap­plic­a­tion. Second, if we choose to sep­ar­ate the gate­way in­ter­face from the gate­way im­ple­ment­a­tion, we can re­place the actual ex­ternal ser­vice with a Ser­vice Stub [EAA] for test­ing.

                                                                                                                                                                                            我们的CreditAgencyGateway 抽象CreditAgencyGateway 和信用机构Web服务 ( CreditAgencyWS ) 之间通信的技术细节。 如果需要,我们可以用测试存根替换 Web 服务。网关接收客户识别号 (SSN) 并从信贷机构获取其他数据。信贷机构返回的两项数据是客户的信用评分和信用记录长度,这两项数据都是完成贷款申请所必需的。该网关包含用于访问 CreditAgencyWS.jws 文件中实现的CreditAgencyWS 的所有客户端代码。

                                                                                                                                                                                            Our Cred­it­A­gency­G­ate­way ab­stracts the tech­nical de­tails of the com­mu­nic­a­tion between the Cred­it­A­gency­G­ate­way and the credit agency Web ser­vice (Cred­it­A­gencyWS). We could re­place the Web ser­vice with a test stub if needed. The gate­way takes in the cus­tomer iden­ti­fic­a­tion number (SSN) and ac­quires ad­di­tional data from the credit agency. The two pieces of data re­turned by the credit agency are the credit score and the length of credit his­tory for the cus­tomer, both of which are needed to com­plete the loan ap­plic­a­tion. This gate­way con­tains all the client-side code to access the Cred­it­A­gencyWS im­ple­men­ted in the Cred­it­A­gencyWS.jws file.

                                                                                                                                                                                            CreditAgencyGateway.java
                                                                                                                                                                                              公共静态 CreditProfile getCustomerCreditProfile(int ssn){
                                                                                                                                                                                            
                                                                                                                                                                                                int 信用分数 = 0;
                                                                                                                                                                                                int 信用历史长度 = 0;
                                                                                                                                                                                            
                                                                                                                                                                                                CreditProfile 信用档案 = null;
                                                                                                                                                                                            
                                                                                                                                                                                                尝试{
                                                                                                                                                                                                  CreditAgencyGateway.readProps();
                                                                                                                                                                                            
                                                                                                                                                                                                  信用档案=新的信用档案();
                                                                                                                                                                                            
                                                                                                                                                                                                     String Creditagency_ep = "http://" + 主机名 + ":" + 端口号 +
                                                                                                                                                                                                                              “/axis/CreditAgencyWS.jws”;
                                                                                                                                                                                            
                                                                                                                                                                                                     整数 i1 = new Integer(ssn);
                                                                                                                                                                                            
                                                                                                                                                                                                     服务service = new Service();
                                                                                                                                                                                                     呼叫 call = (呼叫) service.createCall();
                                                                                                                                                                                                     call.setTargetEndpointAddress( new java.net.URL(creditagency_ep) );
                                                                                                                                                                                            
                                                                                                                                                                                                     call.setOperationName("getCreditHistoryLength");
                                                                                                                                                                                                     call.addParameter( "op1", XMLType.XSD_INT, ParameterMode.IN );
                                                                                                                                                                                                     call.setReturnType( XMLType.XSD_INT );
                                                                                                                                                                                            
                                                                                                                                                                                                     Integer ret1 = (Integer) call.invoke( new Object [] {i1});
                                                                                                                                                                                                     Credit_history_length = ret1.intValue();
                                                                                                                                                                                            
                                                                                                                                                                                                     call.setOperationName("getCreditScore");
                                                                                                                                                                                            
                                                                                                                                                                                                     Integer ret2 = (Integer) call.invoke( new Object [] {i1});
                                                                                                                                                                                                     Credit_score = ret2.intValue();
                                                                                                                                                                                            
                                                                                                                                                                                                     信用档案.setCreditScore(credit_score);
                                                                                                                                                                                                     Creditprofile.setCreditHistoryLength(credit_history_length);
                                                                                                                                                                                                  Thread.sleep(credit_score);
                                                                                                                                                                                                }catch(异常前){
                                                                                                                                                                                                  System.out.println("访问 CreditAgency Web 服务时出错");
                                                                                                                                                                                                }
                                                                                                                                                                                            
                                                                                                                                                                                                返回信用档案;
                                                                                                                                                                                              }
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                              public static Cred­it­Pro­file get­Cus­tomer­Cred­it­Pro­file(int ssn){
                                                                                                                                                                                            
                                                                                                                                                                                                int cred­it_score = 0;
                                                                                                                                                                                                int cred­it_his­tory_length = 0;
                                                                                                                                                                                            
                                                                                                                                                                                                Cred­it­Pro­file cred­it­pro­file = null;
                                                                                                                                                                                            
                                                                                                                                                                                                try{
                                                                                                                                                                                                  Cred­it­A­gency­G­ate­way.read­Props();
                                                                                                                                                                                            
                                                                                                                                                                                                  cred­it­pro­file = new Cred­it­Pro­file();
                                                                                                                                                                                            
                                                                                                                                                                                                     String cred­it­a­gency_ep = "http://" + host­name + ":" + port­num +
                                                                                                                                                                                                                              "/axis/Cred­it­A­gencyWS.jws";
                                                                                                                                                                                            
                                                                                                                                                                                                     In­teger i1 = new In­teger(ssn);
                                                                                                                                                                                            
                                                                                                                                                                                                     Ser­vice  ser­vice = new Ser­vice();
                                                                                                                                                                                                     Call     call    = (Call) ser­vice.cre­ateCall();
                                                                                                                                                                                                     call.setTar­getEnd­pointAd­dress( new java.net.URL(cred­it­a­gency_ep) );
                                                                                                                                                                                            
                                                                                                                                                                                                     call.set­Oper­a­tion­Name("getCreditH­is­toryLength");
                                                                                                                                                                                                     call.ad­dPara­meter( "op1", XM­L­Type.XSD_INT, Para­met­erMode.IN );
                                                                                                                                                                                                     call.setRe­turn­Type( XM­L­Type.XSD_INT );
                                                                                                                                                                                            
                                                                                                                                                                                                     In­teger ret1 = (In­teger) call.invoke( new Object [] {i1});
                                                                                                                                                                                                     cred­it_his­tory_length = ret1.in­t­Value();
                                                                                                                                                                                            
                                                                                                                                                                                                     call.set­Oper­a­tion­Name("getCred­itScore");
                                                                                                                                                                                            
                                                                                                                                                                                                     In­teger ret2 = (In­teger) call.invoke( new Object [] {i1});
                                                                                                                                                                                                     cred­it_score = ret2.in­t­Value();
                                                                                                                                                                                            
                                                                                                                                                                                                     cred­it­pro­file.setCred­itScore(cred­it_score);
                                                                                                                                                                                                     cred­it­pro­file.setCreditH­is­toryLength(cred­it_his­tory_length);
                                                                                                                                                                                                  Thread.sleep(cred­it_score);
                                                                                                                                                                                                }catch(Ex­cep­tion ex){
                                                                                                                                                                                                  System.out.println("Error ac­cess­ing the Cred­it­A­gency Web­ser­vice");
                                                                                                                                                                                                }
                                                                                                                                                                                            
                                                                                                                                                                                                return cred­it­pro­file;
                                                                                                                                                                                              }
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            使用此网关的目的是展示服务器应用程序如何使用 Axis 客户端框架来访问同一服务器或远程服务器上的另一个 Web 服务。

                                                                                                                                                                                            The pur­pose of using this Gate­way is to show how a server ap­plic­a­tion uses the Axis client frame­work to access an­other Web ser­vice either on the same server or on a remote server.

                                                                                                                                                                                            实施信用代理服务

                                                                                                                                                                                            信用机构 Web 服务在CreditAgencyWS.jws中进行编码,如下所示。完成贷款申请所需的两个重要数据是客户信用评分和客户信用记录的长度。信用机构拥有每个具有信用记录的客户的数据,并且可以使用客户识别号进行访问。

                                                                                                                                                                                            The credit agency Web ser­vice is coded in Cred­it­A­gencyWS.jws, shown below. The two sig­ni­fic­ant pieces of data needed to com­plete the loan ap­plic­a­tion are the cus­tomer credit score and the length of the cus­tomer's credit his­tory. The credit agency has this data for each cus­tomer with a credit his­tory, and it can be ac­cessed using the cus­tomer iden­ti­fic­a­tion number.

                                                                                                                                                                                            信用机构WS.jws
                                                                                                                                                                                            public int getCreditScore(int de_ssn) 抛出异常
                                                                                                                                                                                            {
                                                                                                                                                                                              int 信用评分;
                                                                                                                                                                                            
                                                                                                                                                                                              Credit_score = (int)(Math.random()*600+300);
                                                                                                                                                                                            
                                                                                                                                                                                              返回信用分数;
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                            public int getCreditHistoryLength(int de_ssn) 抛出异常
                                                                                                                                                                                            {
                                                                                                                                                                                              int 信用历史长度;
                                                                                                                                                                                            
                                                                                                                                                                                              Credit_history_length = (int)(Math.random()*19+1);
                                                                                                                                                                                            
                                                                                                                                                                                              返回信用历史长度;
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                            public int getCred­itScore(int de_ssn) throws Ex­cep­tion
                                                                                                                                                                                            {
                                                                                                                                                                                              int cred­it_score;
                                                                                                                                                                                            
                                                                                                                                                                                              cred­it_score = (int)(Math.random()*600+300);
                                                                                                                                                                                            
                                                                                                                                                                                              return cred­it_score;
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                            public int getCreditH­is­toryLength(int de_ssn) throws Ex­cep­tion
                                                                                                                                                                                            {
                                                                                                                                                                                              int cred­it_his­tory_length;
                                                                                                                                                                                            
                                                                                                                                                                                              cred­it_his­tory_length = (int)(Math.random()*19+1);
                                                                                                                                                                                            
                                                                                                                                                                                              return cred­it_his­tory_length;
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            以下代码显示CreditAgencyWS.jws文件的 WSDL 文件。该文件由 Axis 服务器自动生成。

                                                                                                                                                                                            The fol­low­ing code shows the WSDL file for the Cred­it­A­gencyWS.jws file. This file was auto­mat­ic­ally gen­er­ated by the Axis server.

                                                                                                                                                                                            <wsdl:定义 xmlns:wsdl =“http://schemas.xmlsoap.org/wsdl/”
                                                                                                                                                                                                              xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
                                                                                                                                                                                                <wsdl:消息名称=“getCreditScoreResponse”>
                                                                                                                                                                                                    <wsdl:part name="getCreditScoreReturn" type="xsd:int"/>
                                                                                                                                                                                                </wsdl:消息>
                                                                                                                                                                                                <wsdl:message name="getCreditHistoryLengthRequest">
                                                                                                                                                                                                    <wsdl:part name="de_ssn" type="xsd:int"/>
                                                                                                                                                                                                </wsdl:消息>
                                                                                                                                                                                                <wsdl:message name="getCreditScoreRequest">
                                                                                                                                                                                                    <wsdl:part name="de_ssn" type="xsd:int"/>
                                                                                                                                                                                                </wsdl:消息>
                                                                                                                                                                                                <wsdl:message name="getCreditHistoryLengthResponse">
                                                                                                                                                                                                    <wsdl:part name="getCreditHistoryLengthReturn" type="xsd:int"/>
                                                                                                                                                                                                </wsdl:消息>
                                                                                                                                                                                                <wsdl:portType name="CreditAgencyWS">
                                                                                                                                                                                                    <wsdl:操作名称=“getCreditHistoryLength”parameterOrder=“de_ssn”>
                                                                                                                                                                                                        <wsdl:输入消息=“intf:getCreditHistoryLengthRequest”
                                                                                                                                                                                                                    名称=“getCreditHistoryLengthRequest”/>
                                                                                                                                                                                                        <wsdl:输出消息=“intf:getCreditHistoryLengthResponse”
                                                                                                                                                                                                                     名称=“getCreditHistoryLengthResponse”/>
                                                                                                                                                                                                    </wsdl:操作>
                                                                                                                                                                                                    <wsdl:操作名称=“getCreditScore”parameterOrder=“de_ssn”>
                                                                                                                                                                                                        <wsdl:输入消息=“intf:getCreditScoreRequest”
                                                                                                                                                                                                                    名称=“getCreditScoreRequest”/>
                                                                                                                                                                                                        <wsdl:输出消息=“intf:getCreditScoreResponse”
                                                                                                                                                                                                                     名称=“getCreditScoreResponse”/>
                                                                                                                                                                                                    </wsdl:操作>
                                                                                                                                                                                                </wsdl:端口类型>
                                                                                                                                                                                                <wsdl:binding name="CreditAgencyWSSoapBinding" type="intf:CreditAgencyWS">
                                                                                                                                                                                                    ...
                                                                                                                                                                                                </wsdl:绑定>
                                                                                                                                                                                                <wsdl:服务名称=“CreditAgencyWSService”>
                                                                                                                                                                                                    <wsdl:端口绑定 =“intf:CreditAgencyWSSoapBinding”名称 =“CreditAgencyWS”>
                                                                                                                                                                                                        <wsdlsoap:地址
                                                                                                                                                                                                         位置=“http://192.168.1.25:8080/axis/CreditAgencyWS.jws”/>
                                                                                                                                                                                                    </wsdl:端口>
                                                                                                                                                                                                </wsdl:服务>
                                                                                                                                                                                            </wsdl:定义>
                                                                                                                                                                                            
                                                                                                                                                                                            <wsdl:defin­i­tions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                                                                                                                                                                                                              xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
                                                                                                                                                                                                <wsdl:mes­sage name="getCred­itScoreResponse">
                                                                                                                                                                                                    <wsdl:part name="getCred­itScoreReturn" type="xsd:int"/>
                                                                                                                                                                                                </wsdl:mes­sage>
                                                                                                                                                                                                <wsdl:mes­sage name="getCreditH­is­toryLeng­thRequest">
                                                                                                                                                                                                    <wsdl:part name="de_ssn" type="xsd:int"/>
                                                                                                                                                                                                </wsdl:mes­sage>
                                                                                                                                                                                                <wsdl:mes­sage name="getCred­itScore­Re­quest">
                                                                                                                                                                                                    <wsdl:part name="de_ssn" type="xsd:int"/>
                                                                                                                                                                                                </wsdl:mes­sage>
                                                                                                                                                                                                <wsdl:mes­sage name="getCreditH­is­toryLeng­thResponse">
                                                                                                                                                                                                    <wsdl:part name="getCreditH­is­toryLeng­thReturn" type="xsd:int"/>
                                                                                                                                                                                                </wsdl:mes­sage>
                                                                                                                                                                                                <wsdl:port­Type name="Cred­it­A­gencyWS">
                                                                                                                                                                                                    <wsdl:op­er­a­tion name="getCreditH­is­toryLength" para­met­erOr­der="de_ssn">
                                                                                                                                                                                                        <wsdl:input mes­sage="intf:getCreditH­is­toryLeng­thRequest"
                                                                                                                                                                                                                    name="getCreditH­is­toryLeng­thRequest"/>
                                                                                                                                                                                                        <wsdl:output mes­sage="intf:getCreditH­is­toryLeng­thResponse"
                                                                                                                                                                                                                     name="getCreditH­is­toryLeng­thResponse"/>
                                                                                                                                                                                                    </wsdl:op­er­a­tion>
                                                                                                                                                                                                    <wsdl:op­er­a­tion name="getCred­itScore" para­met­erOr­der="de_ssn">
                                                                                                                                                                                                        <wsdl:input mes­sage="intf:getCred­itScore­Re­quest"
                                                                                                                                                                                                                    name="getCred­itScore­Re­quest"/>
                                                                                                                                                                                                        <wsdl:output mes­sage="intf:getCred­itScoreResponse"
                                                                                                                                                                                                                     name="getCred­itScoreResponse"/>
                                                                                                                                                                                                    </wsdl:op­er­a­tion>
                                                                                                                                                                                                </wsdl:port­Type>
                                                                                                                                                                                                <wsdl:bind­ing name="Cred­it­A­gency­WS­Soap­Bind­ing" type="intf:Cred­it­A­gencyWS">
                                                                                                                                                                                                    ...
                                                                                                                                                                                                </wsdl:bind­ing>
                                                                                                                                                                                                <wsdl:ser­vice name="Cred­it­A­gency­WSSer­vice">
                                                                                                                                                                                                    <wsdl:port bind­ing="intf:Cred­it­A­gency­WS­Soap­Bind­ing" name="Cred­it­A­gencyWS">
                                                                                                                                                                                                        <wsdlsoap:ad­dress
                                                                                                                                                                                                         loc­a­tion="http://192.168.1.25:8080/axis/Cred­it­A­gencyWS.jws"/>
                                                                                                                                                                                                    </wsdl:port>
                                                                                                                                                                                                </wsdl:ser­vice>
                                                                                                                                                                                            </wsdl:defin­i­tions>
                                                                                                                                                                                            

                                                                                                                                                                                            <wsdl:service>元素定义CreditAgencyWSService 并公开将由客户端(在本例中为贷款经纪人)访问的端点。< wsdl:operation>元素定义CreditAgencyWS.jws文件中定义的两个公共方法getCreditScoregetCreditHistoryLength对于每个方法,都定义了一个请求-响应消息对,如<wsdl:message>元素中所示。这些是getCreditScoreRequest 、 getCreditScoreResponse 、 getCreditHistoryLengthRequest和getCreditHistoryLengthResponse

                                                                                                                                                                                            The <wsdl:ser­vice> ele­ment defines the Cred­it­A­gency­WSSer­vice and ex­poses the en­d­point that will be ac­cessed by the client, in this case the loan broker. The <wsdl:op­er­a­tion> ele­ments define the two public meth­ods defined in the Cred­it­A­gencyWS.jws file, getCred­itScore, and getCreditH­is­toryLength. For each of these meth­ods, a re­quest-re­sponse mes­sage pair is defined, as can be seen in the <wsdl:mes­sage> ele­ments. These are getCred­itScore­Re­quest, getCred­itScoreResponse, getCreditH­is­toryLeng­thRequest, and getCreditH­is­toryLeng­thResponse.

                                                                                                                                                                                            出于空间考虑,我们再次折叠了 WSDL 文件的大部分元素以适应该部分的格式。如果您有兴趣查看整个文件,可以使用以下 URL 在您的服务器上访问它,其中主机名端口号是您的服务器安装的值:

                                                                                                                                                                                            Once again, in the in­terest of space, we have col­lapsed most of the ele­ments of the WSDL file to fit the format of the sec­tion. If you are in­ter­ested in seeing the entire file, it can be ac­cessed on your server using the fol­low­ing URL, where host­name and port­num­ber are the values for your server in­stall­a­tion:

                                                                                                                                                                                            http://主机名:端口号/axis/CreditAgencyWS.jws?wsdl

                                                                                                                                                                                            http://host­name:port­num­ber/axis/Cred­it­A­gencyWS.jws?wsdl

                                                                                                                                                                                            我们示例中的信用机构 Web 服务不提供真正的功能。相反,它会删除实现并返回可供应用程序其他部分使用的虚拟数据。

                                                                                                                                                                                            The credit agency Web ser­vice in our ex­ample does not provide real func­tion­al­ity. Rather, it stubs out the im­ple­ment­a­tion and re­turns dummy data that can be used by other parts of the ap­plic­a­tion.

                                                                                                                                                                                            获取报价

                                                                                                                                                                                            丰富了客户数据后,贷款经纪人现在调用贷款清算所功能,该功能是贷款经纪人本身的一部分。如以下调用所示,贷款清算所接收贷款申请的所有数据。

                                                                                                                                                                                            Having en­riched the cus­tomer data, the loan broker now calls the loan clear­ing­house func­tion, which is part of the loan broker itself. As seen in the fol­low­ing call, the loan clear­ing­house re­ceives all the data for the loan ap­plic­a­tion.

                                                                                                                                                                                            getResultsFromLoanClearingHouse(ssn, 贷款金额, 贷款期限,
                                                                                                                                                                                                                            信用历史长度、信用分数);
                                                                                                                                                                                            
                                                                                                                                                                                            getRes­ults­From­Loan­Clear­ing­House(ssn, loanamount, loan­dur­a­tion,
                                                                                                                                                                                                                            cred­it_his­tory_length, cred­it_score);
                                                                                                                                                                                            

                                                                                                                                                                                            如果应用程序变得更加复杂或者如果贷款经纪人要求更改为使用外部贷款清算所,则贷款清算所功能可以进一步划分为自己的逻辑单元。在我们的示例中,贷款清算所功能可以分为三个步骤:

                                                                                                                                                                                            The loan clear­ing­house func­tion could be fur­ther de­marc­ated into its own lo­gical unit if the ap­plic­a­tion gets more com­plex or if the loan broker re­quire­ments change to use an ex­ternal loan clear­ing­house. In our ex­ample, the loan clear­ing­house func­tion­al­ity can be broken down into three steps:

                                                                                                                                                                                            • 获取可以为客户贷款提供服务的贷方列表。

                                                                                                                                                                                            • Get a list of lenders who can ser­vice the cus­tomer loan.

                                                                                                                                                                                            • 从贷方列表中每家银行的所有报价中获取最佳报价。

                                                                                                                                                                                            • Get the best quote out of all quotes from each bank in the lender list.

                                                                                                                                                                                            • 对最佳报价中的数据进行格式化并将其返回给贷款经纪人。

                                                                                                                                                                                            • Format the data from the best quote and return it to the loan broker.

                                                                                                                                                                                            代码显示了以下步骤:

                                                                                                                                                                                            The code shows these steps:

                                                                                                                                                                                            贷款经纪人WS.jws
                                                                                                                                                                                            私有字符串 getResultsFromLoanClearingHouse(int ssn, 双贷款金额,
                                                                                                                                                                                                           int 贷款期限、int 信用历史长度、int 信用分数) {
                                                                                                                                                                                               String lch_results="贷款清算所结果";
                                                                                                                                                                                            
                                                                                                                                                                                               ArrayList 贷方列表 = LenderGateway.getLenderList
                                                                                                                                                                                                                      (贷款金额、信用历史长度、信用分数);
                                                                                                                                                                                            
                                                                                                                                                                                               BankQuote bestquote = BankQuoteGateway.getBestQuote
                                                                                                                                                                                                        (贷方列表、SSN、贷款金额、贷款期限、信用历史长度、信用分数);
                                                                                                                                                                                            
                                                                                                                                                                                               lch_results = "总共" + lenderlist.size() +
                                                                                                                                                                                                             “引用,最好的引用来自”+
                                                                                                                                                                                                             this.getLoanQuotesReport(bestquote);
                                                                                                                                                                                            
                                                                                                                                                                                               返回lch_结果;
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                            private String getRes­ults­From­Loan­Clear­ing­House(int ssn, double loanamount,
                                                                                                                                                                                                           int loan­dur­a­tion, int cred­it_his­tory_length, int cred­it_score) {
                                                                                                                                                                                               String lch_res­ults="Res­ults from Loan Clear­ing House ";
                                                                                                                                                                                            
                                                                                                                                                                                               Ar­rayL­ist len­der­l­ist = Lender­Gate­way.ge­tLen­der­L­ist
                                                                                                                                                                                                                      (loanamount, cred­it_his­tory_length, cred­it_score);
                                                                                                                                                                                            
                                                                                                                                                                                               BankQuote be­stquote = BankQuoteG­ate­way.get­Be­stQuote
                                                                                                                                                                                                        (len­der­l­ist,ssn,loanamount,loan­dur­a­tion,cred­it_his­tory_length,cred­it_score);
                                                                                                                                                                                            
                                                                                                                                                                                               lch_res­ults = "Out of a total of " + len­der­l­ist.size() +
                                                                                                                                                                                                             " quote(s), the best quote is from" +
                                                                                                                                                                                                             this.getLoan­Quotes­Re­port(be­stquote);
                                                                                                                                                                                            
                                                                                                                                                                                               return lch_res­ults;
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            贷款清算所的首要要求是获取可以为客户贷款申请提供服务的合适贷方列表。为了从贷款经纪人中抽象出贷款人选择过程的功能,我们创建了一个LenderGateway类。我们还可以通过将此功能封装在 Web 服务中来使解决方案变得更有趣。然而,Web 服务应该仔细设计,并且应该考虑参数和返回类型,因为这些类型必须可序列化到网络和从网络序列化。为了简单起见,我们将贷方选择逻辑合并到一个由贷款经纪人调用的简单 Java 类中。

                                                                                                                                                                                            The first re­quire­ment for the loan clear­ing­house is to get a list of suit­able lenders that can ser­vice the cus­tomer loan ap­plic­a­tion. In order to ab­stract the func­tions of the lender se­lec­tion pro­cess from the loan broker, we create a Lender­Gate­way class. We could make the solu­tion more in­ter­est­ing by en­cap­su­lat­ing this func­tion inside a Web ser­vice as well. How­ever, Web ser­vices should be de­signed care­fully, and the para­met­ers and return types should be taken into con­sid­er­a­tion, since the types have to be seri­al­iz­able to and from the net­work. To keep things simple, we in­cor­por­ate the lender se­lec­tion logic in a simple Java class that is called by the loan broker.

                                                                                                                                                                                            如果贷方网关返回银行的服务端点集合,那么对于贷款经纪人来说将是最方便的。这种方法的缺点是银行 Web 服务的标识必须在贷方网关中进行硬编码。这在现实生活中成为维护噩梦,尤其是因为银行被收购、出售或合并的频率比 IT 架构师更换工作的频率还要高。我们将在本节后面讨论 BankQuoteGateway主题时讨论针对银行及其 Web 服务的强大解决方案。

                                                                                                                                                                                            It would be most con­veni­ent for the loan broker if the lender gate­way re­turned a col­lec­tion of ser­vice en­d­points for the banks. The draw­back of this ap­proach is that the iden­ti­fic­a­tion of the bank Web ser­vice would have to be hard-coded in the lender gate­way. This be­comes a main­ten­ance night­mare in real life, es­pe­cially since banks are bought, sold, or merged more often than IT ar­chi­tects change jobs. We dis­cuss a robust solu­tion for the bank and its Web ser­vice in the dis­cus­sion of the BankQuoteG­ate­way topic later in this sec­tion.

                                                                                                                                                                                            getLenderList方法(如下所示)返回可以为贷款请求提供服务的贷方(即银行)集合。

                                                                                                                                                                                            The ge­tLen­der­L­ist method, shown below, re­turns the set of lenders (i.e., banks) that can ser­vice the loan re­quest.

                                                                                                                                                                                            LenderGateway.java
                                                                                                                                                                                            公共静态ArrayList getLenderList(双贷款金额,
                                                                                                                                                                                                                                  int 信用历史长度,
                                                                                                                                                                                                                                  int 信用分数){
                                                                                                                                                                                            
                                                                                                                                                                                              ArrayList 贷方 = new ArrayList();
                                                                                                                                                                                              LenderGateway.readProps();
                                                                                                                                                                                            
                                                                                                                                                                                              if ((贷款金额 >= (double)75000) && (credit_score >= 600) &&
                                                                                                                                                                                                  (credit_history_length >= 8))
                                                                                                                                                                                              {
                                                                                                                                                                                                lenders.add(new Bank1(主机名, 端口号));
                                                                                                                                                                                                lenders.add(new Bank2(主机名, 端口号));
                                                                                                                                                                                              }
                                                                                                                                                                                            
                                                                                                                                                                                              if (((贷款金额 >= (double)10000) && (贷款金额 <= (double)74999)) &&
                                                                                                                                                                                                  (credit_score >= 400) && (credit_history_length >= 3))
                                                                                                                                                                                              {
                                                                                                                                                                                                lenders.add(new Bank3(主机名, 端口号));
                                                                                                                                                                                                lenders.add(new Bank4(主机名, 端口号));
                                                                                                                                                                                              }
                                                                                                                                                                                            
                                                                                                                                                                                              lenders.add(new Bank5(主机名, 端口号));
                                                                                                                                                                                            
                                                                                                                                                                                              返回贷方;
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                            public static Ar­rayL­ist ge­tLen­der­L­ist(double loanamount,
                                                                                                                                                                                                                                  int cred­it_his­tory_length,
                                                                                                                                                                                                                                  int cred­it_score){
                                                                                                                                                                                            
                                                                                                                                                                                              Ar­rayL­ist lenders = new Ar­rayL­ist();
                                                                                                                                                                                              Lender­Gate­way.read­Props();
                                                                                                                                                                                            
                                                                                                                                                                                              if ((loanamount >= (double)75000) && (cred­it_score >= 600) &&
                                                                                                                                                                                                  (cred­it_his­tory_length >= 8))
                                                                                                                                                                                              {
                                                                                                                                                                                                lenders.add(new Bank1(host­name, port­num));
                                                                                                                                                                                                lenders.add(new Bank2(host­name, port­num));
                                                                                                                                                                                              }
                                                                                                                                                                                            
                                                                                                                                                                                              if (((loanamount >= (double)10000) && (loanamount <= (double)74999)) &&
                                                                                                                                                                                                  (cred­it_score >= 400) && (cred­it_his­tory_length >= 3))
                                                                                                                                                                                              {
                                                                                                                                                                                                lenders.add(new Bank3(host­name, port­num));
                                                                                                                                                                                                lenders.add(new Bank4(host­name, port­num));
                                                                                                                                                                                              }
                                                                                                                                                                                            
                                                                                                                                                                                              lenders.add(new Bank5(host­name, port­num));
                                                                                                                                                                                            
                                                                                                                                                                                              return lenders;
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            该方法实现了收件人列表模式。在我们的示例中,规则库由非常简单的if语句组成,这些语句根据一组预定义的条件选择一个或多个银行。我们还为每个客户请求设置了默认选择,以便每个客户请求都至少有一个报价。

                                                                                                                                                                                            This method im­ple­ments the Re­cip­i­ent List pat­tern. In our ex­ample, the rule base con­sists of very simple if state­ments that choose one or more banks based on a set of pre­defined con­di­tions. We have also set a de­fault se­lec­tion for every cus­tomer re­quest so that every cus­tomer re­quest will have at least one quote.

                                                                                                                                                                                            贷款经纪人现在将贷方列表传递到银行报价网关,以开始收集报价并做出选择。我们创建另一个网关,一个名为BankQuoteGateway 的类来抽象银行接口的内部功能。贷款经纪人所需要做的就是向 BankQuoteGateway 请求最佳报价,如以下方法调用所示:

                                                                                                                                                                                            The loan broker now passes the list of lenders to the bank quote gate­way to start gath­er­ing quotes and make a se­lec­tion. We create an­other gate­way, a class named BankQuoteG­ate­way to ab­stract the in­ternal func­tion­ing of the bank in­ter­face. All the loan broker needs to do is re­quest the best quote from the BankQuoteG­ate­way, as shown in the fol­low­ing method call:

                                                                                                                                                                                            BankQuote bestquote = BankQuoteGateway.getBestQuote(lenderlist, ssn, 贷款金额,
                                                                                                                                                                                                                  贷款期限、信用历史长度、信用分数);
                                                                                                                                                                                            
                                                                                                                                                                                            BankQuote be­stquote = BankQuoteG­ate­way.get­Be­stQuote(len­der­l­ist, ssn, loanamount,
                                                                                                                                                                                                                  loan­dur­a­tion,cred­it_his­tory_length,cred­it_score);
                                                                                                                                                                                            

                                                                                                                                                                                            BankQuoteGateway通过获取所有银行的报价来响应贷款经纪人的请求,然后选择最佳报价(即利率最低的报价)。getBestQuote方法如下所示:

                                                                                                                                                                                            The BankQuoteG­ate­way re­sponds to the loan broker re­quest by get­ting the quotes from all the banks and then se­lect­ing the best quote (i.e., the quote with the lowest rate). The get­Be­stQuote method is shown here:

                                                                                                                                                                                            BankQuoteGateway.java
                                                                                                                                                                                            公共静态BankQuote getBestQuote(ArrayList贷方,int ssn,双贷款金额,
                                                                                                                                                                                                                                 国际贷款期限,
                                                                                                                                                                                                                                 int 信用历史长度,
                                                                                                                                                                                                                                 int 信用分数){
                                                                                                                                                                                            
                                                                                                                                                                                              银行报价最低报价 = null;
                                                                                                                                                                                              银行报价当前报价 = null;
                                                                                                                                                                                            
                                                                                                                                                                                              ArrayList 银行报价 = BankQuoteGateway.getBankQuotes(贷方, ssn, 贷款金额,
                                                                                                                                                                                                                     贷款期限、信用历史长度、信用分数);
                                                                                                                                                                                            
                                                                                                                                                                                              迭代器 allquotes =bankquotes.iterator();
                                                                                                                                                                                            
                                                                                                                                                                                              while (allquotes.hasNext()){
                                                                                                                                                                                                if (最低报价 == null){
                                                                                                                                                                                                  最低报价 = (BankQuote)allquotes.next();
                                                                                                                                                                                                }
                                                                                                                                                                                                别的{
                                                                                                                                                                                                  当前报价 = (BankQuote)allquotes.next();
                                                                                                                                                                                                  if (当前报价.getInterestRate() < 最低报价.getInterestRate()){
                                                                                                                                                                                                    最低报价 = 当前报价;
                                                                                                                                                                                                  }
                                                                                                                                                                                                }
                                                                                                                                                                                              }
                                                                                                                                                                                              返回最低报价;
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                            public static BankQuote get­Be­stQuote(Ar­rayL­ist lenders, int ssn, double loanamount,
                                                                                                                                                                                                                                 int loan­dur­a­tion,
                                                                                                                                                                                                                                 int cred­it_his­tory_length,
                                                                                                                                                                                                                                 int cred­it_score){
                                                                                                                                                                                            
                                                                                                                                                                                              BankQuote lowest­quote = null;
                                                                                                                                                                                              BankQuote cur­rentquote = null;
                                                                                                                                                                                            
                                                                                                                                                                                              Ar­rayL­ist bankquotes = BankQuoteG­ate­way.get­BankQuotes(lenders, ssn, loanamount,
                                                                                                                                                                                                                     loan­dur­a­tion, cred­it_his­tory_length, cred­it_score);
                                                                                                                                                                                            
                                                                                                                                                                                              Iter­ator allquotes = bankquotes.iter­ator();
                                                                                                                                                                                            
                                                                                                                                                                                              while (allquotes.has­Next()){
                                                                                                                                                                                                if (lowest­quote == null){
                                                                                                                                                                                                  lowest­quote = (BankQuote)allquotes.next();
                                                                                                                                                                                                }
                                                                                                                                                                                                else{
                                                                                                                                                                                                  cur­rentquote = (BankQuote)allquotes.next();
                                                                                                                                                                                                  if (cur­rentquote.getInt­eres­tRate() < lowest­quote.getInt­eres­tRate()){
                                                                                                                                                                                                    lowest­quote = cur­rentquote;
                                                                                                                                                                                                  }
                                                                                                                                                                                                }
                                                                                                                                                                                              }
                                                                                                                                                                                              return lowest­quote;
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            前面的代码中最重要的一行是对 getBankQuotes 的调用。 该方法不仅执行受控拍卖,还实现了聚合器模式。以下清单显示了getBankQuotes方法:

                                                                                                                                                                                            The most sig­ni­fic­ant line in the pre­ceed­ing code is the call to get­BankQuotes. This method not only per­forms the con­trolled Auc­tion but also im­ple­ments the Ag­greg­ator pat­tern. The fol­low­ing list­ing shows the get­BankQuotes method:

                                                                                                                                                                                            公共静态ArrayList getBankQuotes(ArrayList贷方,int ssn,双贷款金额,
                                                                                                                                                                                                                                  国际贷款期限,
                                                                                                                                                                                                                                  int 信用历史长度,
                                                                                                                                                                                                                                  int 信用分数) {
                                                                                                                                                                                            
                                                                                                                                                                                               ArrayList 银行报价 = new ArrayList();
                                                                                                                                                                                               BankQuote 银行报价 = null;
                                                                                                                                                                                               银行银行=空;
                                                                                                                                                                                            
                                                                                                                                                                                               迭代器银行列表=贷方.iterator();
                                                                                                                                                                                            
                                                                                                                                                                                               while (banklist.hasNext()){
                                                                                                                                                                                                 银行 = (银行)banklist.next();
                                                                                                                                                                                                 银行报价 = 银行.getBankQuote(ssn, 贷款金额, 贷款期限,
                                                                                                                                                                                                                               信用历史长度、信用分数);
                                                                                                                                                                                                 银行报价.add(银行报价);
                                                                                                                                                                                               }
                                                                                                                                                                                              返回银行报价单;
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                            public static Ar­rayL­ist get­BankQuotes(Ar­rayL­ist lenders, int ssn, double loanamount,
                                                                                                                                                                                                                                  int loan­dur­a­tion,
                                                                                                                                                                                                                                  int cred­it_his­tory_length,
                                                                                                                                                                                                                                  int cred­it_score) {
                                                                                                                                                                                            
                                                                                                                                                                                               Ar­rayL­ist bankquotes = new Ar­rayL­ist();
                                                                                                                                                                                               BankQuote bankquote = null;
                                                                                                                                                                                               Bank bank = null;
                                                                                                                                                                                            
                                                                                                                                                                                               Iter­ator bank­list = lenders.iter­ator();
                                                                                                                                                                                            
                                                                                                                                                                                               while (bank­list.has­Next()){
                                                                                                                                                                                                 bank = (Bank)bank­list.next();
                                                                                                                                                                                                 bankquote = bank.get­BankQuote(ssn, loanamount, loan­dur­a­tion,
                                                                                                                                                                                                                               cred­it_his­tory_length, cred­it_score);
                                                                                                                                                                                                 bankquotes.add(bankquote);
                                                                                                                                                                                               }
                                                                                                                                                                                              return bankquotes;
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            受控拍卖的功能是通过while循环实现的。它从贷方列表中提取每家银行,然后调用该方法来生成银行报价,如下面的代码中突出显示的那样。请特别注意方法调用中参数列表的顺序,我们将在开始设计银行和相关 Web 服务时解释其意义。

                                                                                                                                                                                            The func­tion­al­ity of a con­trolled auc­tion is im­ple­men­ted by the while loop. It ex­tracts each bank from the lender list, and then in­vokes the method to gen­er­ate a bank quote, as high­lighted in the code below. Pay spe­cial at­ten­tion to the order of the para­meter list in the call to the method, and we will ex­plain the sig­ni­fic­ance when we start design­ing the banks and the as­so­ci­ated Web ser­vices.

                                                                                                                                                                                            银行.getBankQuote(ssn, 贷款金额, 贷款期限, 信用历史长度, 信用分数);
                                                                                                                                                                                            
                                                                                                                                                                                            bank.get­BankQuote(ssn, loanamount, loan­dur­a­tion, cred­it_his­tory_length, cred­it_score);
                                                                                                                                                                                            

                                                                                                                                                                                            使用银行报价 ArrayList 聚合响应。 getBestQuote方法迭代此银行报价集合并选择最低报价,并将其发送回贷款经纪人。

                                                                                                                                                                                            The re­sponse is ag­greg­ated using the bankquotes Ar­rayL­ist. The get­Be­stQuote method it­er­ates over this col­lec­tion of bank quotes and se­lects the lowest quote, which is sent back to the loan broker.

                                                                                                                                                                                            如前所述,我们将设计银行和银行 Web 服务来模拟现实世界的银行运营,并将银行的功能分开,而不是与贷款经纪人的功能紧密耦合。这将使我们的银行类具有使用前面在 CreditAgencyGateway 类中描述的网关模式的优点。

                                                                                                                                                                                            As men­tioned earlier, we will design the bank and bank Web ser­vice to emu­late a real-world bank op­er­a­tion and keep the func­tions of the bank sep­ar­ate, not tightly coupled with the func­tions of the loan broker. This will let our bank classes have the ad­vant­ages of using the Gate­way pat­tern de­scribed earlier in the Cred­it­A­gency­G­ate­way class.

                                                                                                                                                                                            我们定义一个抽象Bank类如下:

                                                                                                                                                                                            We define an ab­stract Bank class as fol­lows:

                                                                                                                                                                                            银行.java
                                                                                                                                                                                            公共抽象类银行{
                                                                                                                                                                                            
                                                                                                                                                                                              字符串银行名称;
                                                                                                                                                                                              字符串端点=“”;
                                                                                                                                                                                              双倍优惠利率;
                                                                                                                                                                                            
                                                                                                                                                                                              公共银行(字符串主机名,字符串端口号){
                                                                                                                                                                                                this.银行名称 = "";
                                                                                                                                                                                                this.prime_rate = 3.5;
                                                                                                                                                                                              }
                                                                                                                                                                                            
                                                                                                                                                                                              公共无效setEndPoint(字符串endpt){this.endpoint = endpt;}
                                                                                                                                                                                            
                                                                                                                                                                                              public String getBankName(){return this.bankname;}
                                                                                                                                                                                              public String getEndPoint(){return this.endpoint;}
                                                                                                                                                                                              公共双 getPrimeRate(){return this.prime_rate;}
                                                                                                                                                                                              公共摘要 BankQuote getBankQuote(int ssn,双倍贷款金额,int 贷款期限,
                                                                                                                                                                                                                                     int 信用历史记录长度、int 信用分数);
                                                                                                                                                                                            
                                                                                                                                                                                            
                                                                                                                                                                                              公共无效任意等待(){
                                                                                                                                                                                                尝试{
                                                                                                                                                                                                  Thread.sleep((int)(Math.random()*10)*100);
                                                                                                                                                                                            
                                                                                                                                                                                            
                                                                                                                                                                                                }catch(java.lang.InterruptedException intex){
                                                                                                                                                                                                  intex.printStackTrace();
                                                                                                                                                                                                }
                                                                                                                                                                                              }
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                            public ab­stract class Bank {
                                                                                                                                                                                            
                                                                                                                                                                                              String bank­name;
                                                                                                                                                                                              String en­d­point = "";
                                                                                                                                                                                              double prime_rate;
                                                                                                                                                                                            
                                                                                                                                                                                              public Bank(String host­name, String port­num){
                                                                                                                                                                                                this.bank­name = "";
                                                                                                                                                                                                this.prime_rate = 3.5;
                                                                                                                                                                                              }
                                                                                                                                                                                            
                                                                                                                                                                                              public void setEnd­Point(String endpt){this.en­d­point = endpt;}
                                                                                                                                                                                            
                                                                                                                                                                                              public String get­Bank­Name(){return this.bank­name;}
                                                                                                                                                                                              public String getEnd­Point(){return this.en­d­point;}
                                                                                                                                                                                              public double getPrimeR­ate(){return this.prime_rate;}
                                                                                                                                                                                              public ab­stract BankQuote get­BankQuote(int ssn, double loanamount, int loan­dur­a­tion,
                                                                                                                                                                                                                                     int cred­it_his­tory_length, int cred­it_score);
                                                                                                                                                                                            
                                                                                                                                                                                            
                                                                                                                                                                                              public void ar­bit­rary­Wait(){
                                                                                                                                                                                                try{
                                                                                                                                                                                                  Thread.sleep((int)(Math.random()*10)*100);
                                                                                                                                                                                            
                                                                                                                                                                                            
                                                                                                                                                                                                }catch(java.lang.In­ter­rup­te­dEx­cep­tion intex){
                                                                                                                                                                                                  intex.print­Stack­Trace();
                                                                                                                                                                                                }
                                                                                                                                                                                              }
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            在我们的示例中,从银行获取报价的过程大致按照与现实世界银行运营相同的方式进行建模。首先,完成少量的文书工作,然后访问计算机化的汇率报价系统,然后在报价返回到 BankQuoteGateway 之前完成额外的文书工作。

                                                                                                                                                                                            In our ex­ample, the pro­cess for get­ting a quote from a bank is modeled roughly along the same lines a real-world bank op­er­ates. First, a small amount of cler­ical work is done, fol­lowed by an access to the com­pu­ter­ized rate quote system, and then ad­di­tional cler­ical work is done before the quote is re­turned to the BankQuoteG­ate­way.

                                                                                                                                                                                            Bank抽象类和子银行类(Bank1Bank5)对常规银行的操作进行建模在我们的例子中,银行收到贷款请求,职员进行尽职调查,同时验证客户信息是否准确。我们选择将文书工作建模为在Bank抽象类中实现的任意等待方法(如刚才所示),并在getBankQuote方法中调用。为了让事情变得有趣,我们使用 Web 服务对银行的报价系统进行建模,以获取报价。对于给定的银行(银行 n),报价系统使用 BanknWS并在名为的文件中编码银行nWS.jws。有五个银行类别(Bank1Bank5)和五个报价系统(Bank1WSBank5WS)。每个报价系统在方法调用中使用不同的参数列表格式,就像在现实生活中,不同的银行可能使用不同的数据格式一样。这意味着Bank类在调用 Web 服务之前必须使用消息转换器来转换消息的格式。我们将在讨论银行课程后展示这一点。

                                                                                                                                                                                            The Bank ab­stract class and the child bank classes (Bank1 to Bank5) model the op­er­a­tion of a reg­u­lar bank. In our ex­ample, the bank re­ceives the loan re­quest, and the cler­ical staff con­ducts the due di­li­gence re­search while veri­fy­ing that the cus­tomer in­form­a­tion is ac­cur­ate. We chose to model the cler­ical work as an ar­bit­rary wait method im­ple­men­ted in the Bank ab­stract class, as just shown, and in­voked in the get­BankQuote method. To make things in­ter­est­ing, we use Web ser­vices to model the bank's rate quote system for get­ting rate quotes. For a given bank (bank n), the rate quote system is modeled using a BanknWS and coded in a file named BanknWS.jws. There are five bank classes (Bank1 to Bank5) and five rate quote sys­tems (Bank1WS to Bank5WS). Each of the rate quote sys­tems uses a dif­fer­ent format for the para­meter list in the method calljust like, in real life, dif­fer­ent banks are likely to use dif­fer­ent data formats. This means the Bank class has to use a Mes­sage Trans­lator to trans­late the format of the mes­sage before call­ing the Web ser­vice. We will show this after dis­cuss­ing the Bank classes.

                                                                                                                                                                                            请注意,抽象Bank类中的getBankQuote方法是一个抽象方法,并且具有按特定格式排序的参数。我们现在看看其中一个银行实现,并且没有特殊原因,选择Bank1。所有银行的类结构都是相同的,每个银行的区别仅在于其字段的值(银行名称和端点地址),这些字段是在构建银行对象时设置的。

                                                                                                                                                                                            Note that the get­BankQuote method in the ab­stract Bank class is an ab­stract method and has the para­met­ers ordered in a par­tic­u­lar format. We now look at one of the bank im­ple­ment­a­tions and, for no par­tic­u­lar reason, choose Bank1. The class struc­ture of all the banks will be identical, and each will differ only in the values of its fields (the bank name and en­d­point ad­dress), which are set when the bank object is con­struc­ted.

                                                                                                                                                                                            银行1.java
                                                                                                                                                                                            公共类 Bank1 扩展了 Bank {
                                                                                                                                                                                            
                                                                                                                                                                                              公共 Bank1(字符串主机名,字符串端口号){
                                                                                                                                                                                                超级(主机名,端口号);
                                                                                                                                                                                                Bankname = "乡村俱乐部专属银行家\n";
                                                                                                                                                                                                String ep1 = "http://" + 主机名 + ":" + 端口号 + "/axis/Bank1WS.jws";
                                                                                                                                                                                                this.setEndPoint(ep1);
                                                                                                                                                                                              }
                                                                                                                                                                                            
                                                                                                                                                                                              公共无效setEndPoint(字符串endpt){this.endpoint = endpt;}
                                                                                                                                                                                            
                                                                                                                                                                                              public String getBankName(){return this.bankname;}
                                                                                                                                                                                              public String getEndPoint(){return this.endpoint;}
                                                                                                                                                                                            
                                                                                                                                                                                              public BankQuote getBankQuote(int ssn, 双倍贷款金额, int 贷款期限,
                                                                                                                                                                                                                            int Credit_history_length, int Credit_score) {
                                                                                                                                                                                            
                                                                                                                                                                                                BankQuote 银行报价 = new BankQuote();
                                                                                                                                                                                            
                                                                                                                                                                                                整数 i1 = new Integer(ssn);
                                                                                                                                                                                                双倍 i2 = new Double(prime_rate);
                                                                                                                                                                                                Double i3 = new Double(贷款金额);
                                                                                                                                                                                                整数 i4 = new Integer(loanduration);
                                                                                                                                                                                                整数 i5 = new Integer(credit_history_length);
                                                                                                                                                                                                整数 i6 = new Integer(credit_score);
                                                                                                                                                                                            
                                                                                                                                                                                                尝试{
                                                                                                                                                                                                  服务service = new Service();
                                                                                                                                                                                                  呼叫 call = (呼叫) service.createCall();
                                                                                                                                                                                                  call.setTargetEndpointAddress( new java.net.URL(endpoint) );
                                                                                                                                                                                            
                                                                                                                                                                                                  call.setOperationName("getQuote");
                                                                                                                                                                                            
                                                                                                                                                                                                  call.addParameter( "op1", XMLType.XSD_INT, ParameterMode.IN );
                                                                                                                                                                                                  call.addParameter( "op2", XMLType.XSD_DOUBLE, ParameterMode.IN );
                                                                                                                                                                                                  call.addParameter( "op3", XMLType.XSD_DOUBLE, ParameterMode.IN );
                                                                                                                                                                                                  call.addParameter( "op4", XMLType.XSD_INT, ParameterMode.IN );
                                                                                                                                                                                                  call.addParameter( "op5", XMLType.XSD_INT, ParameterMode.IN );
                                                                                                                                                                                                  call.addParameter( "op6", XMLType.XSD_INT, ParameterMode.IN );
                                                                                                                                                                                            
                                                                                                                                                                                                  call.setReturnType( XMLType.XSD_DOUBLE);
                                                                                                                                                                                            
                                                                                                                                                                                                  双倍利率 = (Double) call.invoke( new Object [] {i1,i2,i3,i4,i5,i6});
                                                                                                                                                                                            
                                                                                                                                                                                                  银行报价.setBankName(银行名称);
                                                                                                                                                                                                  银行报价.setInterestRate(interestrate.doubleValue());
                                                                                                                                                                                                }catch(异常前){
                                                                                                                                                                                                 System.err.println("从 " + 银行名称访问轴 Web 服务时出错);
                                                                                                                                                                                            
                                                                                                                                                                                                  BankQuote badbq = new BankQuote();
                                                                                                                                                                                                  badbq.setBankName("WS 中出现错误");
                                                                                                                                                                                                  返回badbq;
                                                                                                                                                                                                }
                                                                                                                                                                                            
                                                                                                                                                                                                任意等待();
                                                                                                                                                                                            
                                                                                                                                                                                                返回银行报价单;
                                                                                                                                                                                              }
                                                                                                                                                                                            
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                            public class Bank1 ex­tends Bank {
                                                                                                                                                                                            
                                                                                                                                                                                              public Bank1(String host­name, String port­num){
                                                                                                                                                                                                super(host­name,port­num);
                                                                                                                                                                                                bank­name = "Ex­clus­ive Coun­try Club Bankers\n";
                                                                                                                                                                                                String ep1 = "http://" + host­name + ":" + port­num + "/axis/Bank1WS.jws";
                                                                                                                                                                                                this.setEnd­Point(ep1);
                                                                                                                                                                                              }
                                                                                                                                                                                            
                                                                                                                                                                                              public void setEnd­Point(String endpt){this.en­d­point = endpt;}
                                                                                                                                                                                            
                                                                                                                                                                                              public String get­Bank­Name(){return this.bank­name;}
                                                                                                                                                                                              public String getEnd­Point(){return this.en­d­point;}
                                                                                                                                                                                            
                                                                                                                                                                                              public BankQuote get­BankQuote(int ssn, double loanamount, int loan­dur­a­tion,
                                                                                                                                                                                                                            int cred­it_his­tory_length, int cred­it_score) {
                                                                                                                                                                                            
                                                                                                                                                                                                BankQuote bankquote = new BankQuote();
                                                                                                                                                                                            
                                                                                                                                                                                                In­teger i1 = new In­teger(ssn);
                                                                                                                                                                                                Double i2 = new Double(prime_rate);
                                                                                                                                                                                                Double i3 = new Double(loanamount);
                                                                                                                                                                                                In­teger i4 = new In­teger(loan­dur­a­tion);
                                                                                                                                                                                                In­teger i5 = new In­teger(cred­it_his­tory_length);
                                                                                                                                                                                                In­teger i6 = new In­teger(cred­it_score);
                                                                                                                                                                                            
                                                                                                                                                                                                try{
                                                                                                                                                                                                  Ser­vice ser­vice = new Ser­vice();
                                                                                                                                                                                                  Call call = (Call) ser­vice.cre­ateCall();
                                                                                                                                                                                                  call.setTar­getEnd­pointAd­dress( new java.net.URL(en­d­point) );
                                                                                                                                                                                            
                                                                                                                                                                                                  call.set­Oper­a­tion­Name("getQuote");
                                                                                                                                                                                            
                                                                                                                                                                                                  call.ad­dPara­meter( "op1", XM­L­Type.XSD_INT, Para­met­erMode.IN );
                                                                                                                                                                                                  call.ad­dPara­meter( "op2", XM­L­Type.XSD_­DOUBLE, Para­met­erMode.IN );
                                                                                                                                                                                                  call.ad­dPara­meter( "op3", XM­L­Type.XSD_­DOUBLE, Para­met­erMode.IN );
                                                                                                                                                                                                  call.ad­dPara­meter( "op4", XM­L­Type.XSD_INT, Para­met­erMode.IN );
                                                                                                                                                                                                  call.ad­dPara­meter( "op5", XM­L­Type.XSD_INT, Para­met­erMode.IN );
                                                                                                                                                                                                  call.ad­dPara­meter( "op6", XM­L­Type.XSD_INT, Para­met­erMode.IN );
                                                                                                                                                                                            
                                                                                                                                                                                                  call.setRe­turn­Type( XM­L­Type.XSD_­DOUBLE);
                                                                                                                                                                                            
                                                                                                                                                                                                  Double in­teres­trate = (Double) call.invoke( new Object [] {i1,i2,i3,i4,i5,i6});
                                                                                                                                                                                            
                                                                                                                                                                                                  bankquote.set­Bank­Name(bank­name);
                                                                                                                                                                                                  bankquote.setInt­eres­tRate(in­teres­trate.doubl­e­Value());
                                                                                                                                                                                                }catch(Ex­cep­tion ex){
                                                                                                                                                                                                 System.err.println("Error ac­cess­ing the axis web­ser­vice from " + bank­name);
                                                                                                                                                                                            
                                                                                                                                                                                                  BankQuote badbq = new BankQuote();
                                                                                                                                                                                                  badbq.set­Bank­Name("ERROR in WS");
                                                                                                                                                                                                  return badbq;
                                                                                                                                                                                                }
                                                                                                                                                                                            
                                                                                                                                                                                                ar­bit­rary­Wait();
                                                                                                                                                                                            
                                                                                                                                                                                                return bankquote;
                                                                                                                                                                                              }
                                                                                                                                                                                            
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            如前面的代码所示,已实现getBankQuote方法并具有按特定顺序排列的参数。

                                                                                                                                                                                            As seen in the pre­ceed­ing code, the get­BankQuote method is im­ple­men­ted and has the para­met­ers in a par­tic­u­lar order.

                                                                                                                                                                                            public BankQuote getBankQuote(int ssn, 双倍贷款金额, int 贷款期限,
                                                                                                                                                                                                                          int 信用历史长度、int 信用分数)
                                                                                                                                                                                            
                                                                                                                                                                                            public BankQuote get­BankQuote(int ssn, double loanamount, int loan­dur­a­tion,
                                                                                                                                                                                                                          int cred­it_his­tory_length, int cred­it_score)
                                                                                                                                                                                            
                                                                                                                                                                                            实施银行业务

                                                                                                                                                                                            如前所述,每个银行的报价系统参数的格式是不同的。这意味着Bank类的getBankQuote方法实现了消息转换器模式,并且必须在调用相应的银行 Web 服务之前转换参数的顺序。每个银行 Web 服务的getQuote方法的签名如下所示。

                                                                                                                                                                                            As de­scribed earlier, the format for the para­met­ers of the rate quote system for each bank is dif­fer­ent. This means the get­BankQuote method of the Bank class im­ple­ments the Mes­sage Trans­lator pat­tern and has to trans­late the order of the para­met­ers before call­ing the re­spect­ive bank Web ser­vice. The sig­na­ture of the method getQuote for each bank Web ser­vice is shown below.

                                                                                                                                                                                            银行1WS:

                                                                                                                                                                                            Bank1WS:

                                                                                                                                                                                            getQuote(int ssn, 双 prime_rate, 双贷款金额, int 贷款期限,
                                                                                                                                                                                                     int 信用历史长度、int 信用分数)
                                                                                                                                                                                            
                                                                                                                                                                                            getQuote(int ssn, double prime_rate, double loanamount, int loan­dur­a­tion,
                                                                                                                                                                                                     int cred­it_his­tory_length, int cred­it_score)
                                                                                                                                                                                            

                                                                                                                                                                                            银行2WS:

                                                                                                                                                                                            Bank2WS:

                                                                                                                                                                                            getQuote(双倍 prime_rate, 双倍贷款金额, int 贷款期限,
                                                                                                                                                                                                     int 信用历史长度、int 信用分数、int ssn)
                                                                                                                                                                                            
                                                                                                                                                                                            getQuote(double prime_rate, double loanamount, int loan­dur­a­tion,
                                                                                                                                                                                                     int cred­it_his­tory_length, int cred­it_score, int ssn)
                                                                                                                                                                                            

                                                                                                                                                                                            银行3WS:

                                                                                                                                                                                            Bank3WS:

                                                                                                                                                                                            getQuote(双贷款金额, int 贷款期限, int 信用历史长度,
                                                                                                                                                                                                     int 信用分数、int ssn、双倍 prime_rate)
                                                                                                                                                                                            
                                                                                                                                                                                            getQuote(double loanamount, int loan­dur­a­tion, int cred­it_his­tory_length,
                                                                                                                                                                                                     int cred­it_score, int ssn, double prime_rate)
                                                                                                                                                                                            

                                                                                                                                                                                            银行4WS:

                                                                                                                                                                                            Bank4WS:

                                                                                                                                                                                            getQuote(int 贷款期限、int 信用历史长度、int 信用分数、int ssn、
                                                                                                                                                                                                     双倍优惠利率,双倍贷款金额)
                                                                                                                                                                                            
                                                                                                                                                                                            getQuote(int loan­dur­a­tion, int cred­it_his­tory_length, int cred­it_score, int ssn,
                                                                                                                                                                                                     double prime_rate, double loanamount)
                                                                                                                                                                                            

                                                                                                                                                                                            银行5WS:

                                                                                                                                                                                            Bank5WS:

                                                                                                                                                                                            getQuote(int Credit_history_length, int Credit_score, int ssn, double prime_rate,
                                                                                                                                                                                                     双倍贷款金额,国际贷款期限)
                                                                                                                                                                                            
                                                                                                                                                                                            getQuote(int cred­it_his­tory_length, int cred­it_score, int ssn, double prime_rate,
                                                                                                                                                                                                     double loanamount, int loan­dur­a­tion)
                                                                                                                                                                                            

                                                                                                                                                                                            getQuote方法的实际实现是一个占位符,并使用简单的算法返回报价,如下所示的Bank1WS.jws

                                                                                                                                                                                            The actual im­ple­ment­a­tion of the getQuote method is a place­holder and re­turns a rate quote using a simple al­gorithm, as shown below for Bank1WS.jws.

                                                                                                                                                                                            银行1WS.jws
                                                                                                                                                                                            公共类 Bank1WS {
                                                                                                                                                                                            
                                                                                                                                                                                              公共双 getQuote(int ssn, 双 prime_rate, 双贷款金额,
                                                                                                                                                                                                                     int 贷款期限、int 信用历史长度、int 信用分数)
                                                                                                                                                                                              {
                                                                                                                                                                                                双倍费率溢价 = 1.5;
                                                                                                                                                                                            
                                                                                                                                                                                                double int_rate = prime_rate + 费率premium + (double)(loanduration/12)/10 +
                                                                                                                                                                                                                  (双)(Math.random()*10)/10;
                                                                                                                                                                                            
                                                                                                                                                                                                返回int_rate;
                                                                                                                                                                                              }
                                                                                                                                                                                            
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                            public class Bank1WS {
                                                                                                                                                                                            
                                                                                                                                                                                              public double getQuote(int ssn, double prime_rate, double loanamount,
                                                                                                                                                                                                                     int loan­dur­a­tion, int cred­it_his­tory_length, int cred­it_score)
                                                                                                                                                                                              {
                                                                                                                                                                                                double rate­premium = 1.5;
                                                                                                                                                                                            
                                                                                                                                                                                                double in­t_rate = prime_rate + rate­premium + (double)(loan­dur­a­tion/12)/10 +
                                                                                                                                                                                                                  (double)(Math.random()*10)/10;
                                                                                                                                                                                            
                                                                                                                                                                                                return in­t_rate;
                                                                                                                                                                                              }
                                                                                                                                                                                            
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            在实际应用中,该公式更加详细和复杂。getQuote方法的返回类型是一个双精度数字,表示银行根据贷款申请中的参数向客户提供的利率。

                                                                                                                                                                                            In a real-world ap­plic­a­tion, the for­mula is a lot more de­tailed and com­plic­ated. The return type of the getQuote method is a double pre­ci­sion number rep­res­ent­ing the rate the bank offers the cus­tomer, given the para­met­ers in the loan ap­plic­a­tion.

                                                                                                                                                                                            Axis 服务器再次自动为每个 Bank JWS 文件生成一个 WSDL 文件(在http://hostname:portnum/axis/Bank1WS.jws?wsdl中公开)。其他银行的 WSDL 文件将具有类似的格式,但参数的定义有所不同。Bank1WS.jws Web 服务的 WSDL 文件如下所示。

                                                                                                                                                                                            Once again, the Axis server auto­mat­ic­ally gen­er­ates a WSDL file for each Bank JWS file (ex­posed at http://host­name:port­num/axis/Bank1WS.jws?wsdl). The WSDL files for the other banks will have a sim­ilar format but will differ in the defin­i­tion of the para­met­ers. The WSDL file for the Bank1WS.jws Web ser­vice is shown below.

                                                                                                                                                                                            <wsdl:定义 xmlns:wsdl =“http://schemas.xmlsoap.org/wsdl/”
                                                                                                                                                                                                              xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
                                                                                                                                                                                                <wsdl:消息名称=“getQuoteRequest”>
                                                                                                                                                                                                    <wsdl:part name="ssn" type="xsd:int"/>
                                                                                                                                                                                                    <wsdl:part name="prime_rate" type="xsd:double"/>
                                                                                                                                                                                                    <wsdl:part name="loanamount" type="xsd:double"/>
                                                                                                                                                                                                    <wsdl:part name="loanduration" type="xsd:int"/>
                                                                                                                                                                                                    <wsdl:part name="credit_history_length" type="xsd:int"/>
                                                                                                                                                                                                    <wsdl:part name="credit_score" type="xsd:int"/>
                                                                                                                                                                                                </wsdl:消息>
                                                                                                                                                                                                <wsdl:消息名称=“getQuoteResponse”>
                                                                                                                                                                                                    <wsdl:part name="getQuoteReturn" type="xsd:double"/>
                                                                                                                                                                                                </wsdl:消息>
                                                                                                                                                                                                <wsdl:portType name="Bank1WS">
                                                                                                                                                                                                    <wsdl:操作名称=“getQuote”parameterOrder=“ssn prime_rate贷款金额贷款期限”
                                                                                                                                                                                                      信用历史长度 信用分数">
                                                                                                                                                                                                        <wsdl:input message="intf:getQuoteRequest" name="getQuoteRequest"/>
                                                                                                                                                                                                        <wsdl:output message="intf:getQuoteResponse" name="getQuoteResponse"/>
                                                                                                                                                                                                    </wsdl:操作>
                                                                                                                                                                                                </wsdl:端口类型>
                                                                                                                                                                                                <wsdl:binding name="Bank1WSSoapBinding" type="intf:Bank1WS">
                                                                                                                                                                                                    ...
                                                                                                                                                                                                </wsdl:绑定>
                                                                                                                                                                                                <wsdl:服务名称=“Bank1WSService”>
                                                                                                                                                                                                    <wsdl:端口绑定 =“intf:Bank1WSSoapBinding”名称 =“Bank1WS”>
                                                                                                                                                                                                        <wsdlsoap:地址位置=“http://192.168.1.25:8080/axis/Bank1WS.jws”/>
                                                                                                                                                                                                    </wsdl:端口>
                                                                                                                                                                                                </wsdl:服务>
                                                                                                                                                                                            </wsdl:定义>
                                                                                                                                                                                            
                                                                                                                                                                                            <wsdl:defin­i­tions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                                                                                                                                                                                                              xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
                                                                                                                                                                                                <wsdl:mes­sage name="getQuote­Re­quest">
                                                                                                                                                                                                    <wsdl:part name="ssn" type="xsd:int"/>
                                                                                                                                                                                                    <wsdl:part name="prime_rate" type="xsd:double"/>
                                                                                                                                                                                                    <wsdl:part name="loanamount" type="xsd:double"/>
                                                                                                                                                                                                    <wsdl:part name="loan­dur­a­tion" type="xsd:int"/>
                                                                                                                                                                                                    <wsdl:part name="cred­it_his­tory_length" type="xsd:int"/>
                                                                                                                                                                                                    <wsdl:part name="cred­it_score" type="xsd:int"/>
                                                                                                                                                                                                </wsdl:mes­sage>
                                                                                                                                                                                                <wsdl:mes­sage name="getQuo­teResponse">
                                                                                                                                                                                                    <wsdl:part name="getQuoteReturn" type="xsd:double"/>
                                                                                                                                                                                                </wsdl:mes­sage>
                                                                                                                                                                                                <wsdl:port­Type name="Bank1WS">
                                                                                                                                                                                                    <wsdl:op­er­a­tion name="getQuote" para­met­erOr­der="ssn prime_rate loanamount loan­dur­a­tion
                                                                                                                                                                                                      cred­it_his­tory_length cred­it_score">
                                                                                                                                                                                                        <wsdl:input mes­sage="intf:getQuote­Re­quest" name="getQuote­Re­quest"/>
                                                                                                                                                                                                        <wsdl:output mes­sage="intf:getQuo­teResponse" name="getQuo­teResponse"/>
                                                                                                                                                                                                    </wsdl:op­er­a­tion>
                                                                                                                                                                                                </wsdl:port­Type>
                                                                                                                                                                                                <wsdl:bind­ing name="Bank1WS­Soap­Bind­ing" type="intf:Bank1WS">
                                                                                                                                                                                                    ...
                                                                                                                                                                                                </wsdl:bind­ing>
                                                                                                                                                                                                <wsdl:ser­vice name="Bank1WSSer­vice">
                                                                                                                                                                                                    <wsdl:port bind­ing="intf:Bank1WS­Soap­Bind­ing" name="Bank1WS">
                                                                                                                                                                                                        <wsdlsoap:ad­dress loc­a­tion="http://192.168.1.25:8080/axis/Bank1WS.jws"/>
                                                                                                                                                                                                    </wsdl:port>
                                                                                                                                                                                                </wsdl:ser­vice>
                                                                                                                                                                                            </wsdl:defin­i­tions>
                                                                                                                                                                                            

                                                                                                                                                                                            正如我们所看到的,WSDL 包含一个操作getQuote ,它在<wsdl:operation>元素中定义,Axis 将其映射到Bank1WS.jws类中的getQuote方法。<wsdl:service>元素定义 Web 服务Bank1WS。有一个请求-响应消息对,每个消息对都在<wsdl:message>元素中定义:getQuoteRequest 和getQuoteResponse

                                                                                                                                                                                            As we can see, the WSDL con­tains one op­er­a­tion, getQuote, defined in the <wsdl:op­er­a­tion> ele­ment, which Axis maps to the getQuote method in the Bank1WS.jws class. The <wsdl:ser­vice> ele­ment defines the Web ser­vice Bank1WS. There is a re­quest-re­sponse mes­sage pair, each defined in a <wsdl:mes­sage> ele­ment: getQuote­Re­quest and getQuo­teResponse.

                                                                                                                                                                                            每个银行的利率报价都在银行报价 bean ( BankQuote )中设置,该 bean 将添加到发送回 BankQuoteGateway 的集合中。 该 bean 不需要任何格式,并且所有银行返回的 bean 看起来基本相同。这消除了规范器将回复消息转换为通用格式的需要。

                                                                                                                                                                                            The rate quote for each bank is set in a bank quote bean (BankQuote), which is added to the col­lec­tion sent back to the BankQuoteG­ate­way. The bean does not re­quire any format­ting, and the beans re­turned by all the banks look es­sen­tially the same. This elim­in­ates the need for a Nor­mal­izer to con­vert the reply mes­sages to a common format.

                                                                                                                                                                                            BankQuoteGateway从它返回的银行报价集合中选择最低报价,并将一个银行报价 bean 发送回贷款经纪人。贷款经纪人访问 bean 中的数据并格式化报告以发送回客户端应用程序。格式化报告的方法如下所示。

                                                                                                                                                                                            The BankQuoteG­ate­way se­lects the lowest quote from the col­lec­tion of bank quotes it gets back and sends one bank quote bean back to the loan broker. The loan broker ac­cesses the data in the bean and formats a report to send back to the client ap­plic­a­tion. The method that formats the report is shown below.

                                                                                                                                                                                            BankQuoteGateway.java
                                                                                                                                                                                            私有静态字符串 getLoanQuotesReport(BankQuote bestquote){
                                                                                                                                                                                                String 银行名称 = bestquote.getBankName();
                                                                                                                                                                                                双倍最佳率 = ((双)((长)(bestquote.getInterestRate()*1000))/(双)1000);
                                                                                                                                                                                            
                                                                                                                                                                                                String results = "\n银行名称:" + 银行名称 + "利率:" + bestrate;
                                                                                                                                                                                            
                                                                                                                                                                                                返回结果;
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                            private static String getLoan­Quotes­Re­port(BankQuote be­stquote){
                                                                                                                                                                                                String bank­name = be­stquote.get­Bank­Name();
                                                                                                                                                                                                double be­strate = ((double)((long)(be­stquote.getInt­eres­tRate()*1000))/(double)1000);
                                                                                                                                                                                            
                                                                                                                                                                                                String res­ults = "\nBank Name: " + bank­name + "In­terest Rate: " + be­strate;
                                                                                                                                                                                            
                                                                                                                                                                                                return res­ults;
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            客户申请

                                                                                                                                                                                            Client Ap­plic­a­tion

                                                                                                                                                                                            客户端应用程序是客户与贷款经纪人应用程序的接口。客户的主要要求是在具有充分错误检查的功能用户界面环境中从客户收集信息。在远离客户视线的幕后,客户端应用程序准备三部分数据以传送到服务器应用程序的端点。为简单起见,我们将客户端应用程序设计为一个 Java 类,其 main 方法将客户端信息作为命令行参数接收。实际上,用户界面可以实现为基于窗口的胖客户端或基于浏览器的瘦客户端。客户端也可以是部署了客户端编程模型的另一个业务系统的一部分。

                                                                                                                                                                                            The client ap­plic­a­tion is the cus­tomer's in­ter­face to the loan broker ap­plic­a­tion. The main re­quire­ment of the client is to gather in­form­a­tion from the cus­tomer in a func­tional user in­ter­face en­vir­on­ment with ad­equate error check­ing. Be­neath the covers and far away from the eyes of the cus­tomer, the client ap­plic­a­tion pre­pares the three pieces of data for de­liv­ery to the en­d­point of the server ap­plic­a­tion. For sim­pli­city, we de­signed the client ap­plic­a­tion to be a Java class with a main method that takes in the client in­form­a­tion as com­mand-line ar­gu­ments. In real­ity, the user in­ter­face could be im­ple­men­ted as a window-based fat client or a browser-based thin client. The client could also be part of an­other busi­ness system that has the client-pro­gram­ming model de­ployed. The most sig­ni­fic­ant part of the client ap­plic­a­tion with re­spect to in­vok­ing the loan broker Web ser­vice is shown here:

                                                                                                                                                                                            服务service = new Service();
                                                                                                                                                                                            呼叫 call = (呼叫) service.createCall();
                                                                                                                                                                                            
                                                                                                                                                                                            call.setTargetEndpointAddress( new java.net.URL(endpoint) );
                                                                                                                                                                                            call.setOperationName(“getLoanQuote”);
                                                                                                                                                                                            call.addParameter( "op1", XMLType.XSD_INT, ParameterMode.IN );
                                                                                                                                                                                            call.addParameter( "op2", XMLType.XSD_DOUBLE, ParameterMode.IN );
                                                                                                                                                                                            call.addParameter( "op3", XMLType.XSD_INT, ParameterMode.IN );
                                                                                                                                                                                            
                                                                                                                                                                                            call.setReturnType( XMLType.XSD_STRING );
                                                                                                                                                                                            
                                                                                                                                                                                            String ret = (String) call.invoke( new Object [] {ssn, 贷款金额, 贷款期限});
                                                                                                                                                                                            
                                                                                                                                                                                            Ser­vice ser­vice = new Ser­vice();
                                                                                                                                                                                            Call     call   = (Call) ser­vice.cre­ateCall();
                                                                                                                                                                                            
                                                                                                                                                                                            call.setTar­getEnd­pointAd­dress( new java.net.URL(en­d­point) );
                                                                                                                                                                                            call.set­Oper­a­tion­Name( "getLoan­Quote" );
                                                                                                                                                                                            call.ad­dPara­meter( "op1", XM­L­Type.XSD_INT, Para­met­erMode.IN );
                                                                                                                                                                                            call.ad­dPara­meter( "op2", XM­L­Type.XSD_­DOUBLE, Para­met­erMode.IN );
                                                                                                                                                                                            call.ad­dPara­meter( "op3", XM­L­Type.XSD_INT, Para­met­erMode.IN );
                                                                                                                                                                                            
                                                                                                                                                                                            call.setRe­turn­Type( XM­L­Type.XSD_STRING );
                                                                                                                                                                                            
                                                                                                                                                                                            String ret = (String) call.invoke( new Object [] {ssn, loanamount, loan­dur­a­tion});
                                                                                                                                                                                            

                                                                                                                                                                                            代码中的重点是定义方法名称 ( getLoanQuotes )的行以及设置参数和返回类型的行。getLoanQuotes方法接收客户 ID、贷款金额和贷款期限。返回值是一个字符串。

                                                                                                                                                                                            The sa­li­ent points in the code are the lines that define the method name (getLoan­Quotes) and those that set the para­met­ers and return types. The method getLoan­Quotes takes in a cus­tomer ID, loan amount, and loan dur­a­tion. The return value is a string.

                                                                                                                                                                                            由于我们没有使用 UDDI(Web 服务查找服务),因此我们对 Web 服务的端点 URL 进行硬编码。由于我们选择将贷款经纪人应用程序部署为 JWS 文件,因此端点具有标准格式,如 Axis API 为 JWS 定义的那样。我们部署的端点显示在以下 URL 中,其中主机名端口号是与您的服务器安装相对应的值,

                                                                                                                                                                                            Since we are not using UDDI, the Web ser­vices lookup ser­vice, we hard-code the en­d­point URL for our Web ser­vice. Since we chose to deploy the loan broker ap­plic­a­tion as a JWS file, the en­d­point has the stand­ard format, as defined by the Axis API for a JWS. The en­d­point for our de­ploy­ment is shown in the fol­low­ing URL, where host­name and port­num­ber are the values cor­res­pond­ing to your server in­stall­a­tion,

                                                                                                                                                                                            http://主机名:端口号/axis/LoanBroker.jws

                                                                                                                                                                                            http://host­name:port­num­ber/axis/Loan­Broker.jws

                                                                                                                                                                                            一直被阻止等待响应的客户端应用程序将接受格式化报告并将其显示在为其设置的 GUI 区域中。或者,可以将其保存或发送到打印机。

                                                                                                                                                                                            The client ap­plic­a­tion, which was blocked all along wait­ing for the re­sponse, will accept the format­ted report and dis­play it in the GUI area set up for it. Al­tern­at­ively, it can be saved or sent to a printer.

                                                                                                                                                                                            运行解决方案

                                                                                                                                                                                            Run­ning the Solu­tion

                                                                                                                                                                                            本节假设您已安装 Axis 和贷款经纪人应用程序。服务器现在需要重新启动或启动(如果未运行)。请按照 Tomcat 帮助文件中的文档了解启动或重新启动服务器的正确步骤。然后,您可以按照以下步骤验证 Tomcat 和 Apache Axis 是否已启动并正在运行:

                                                                                                                                                                                            This sec­tion as­sumes that you have Axis and the loan broker ap­plic­a­tion in­stalled. The server now needs to be re­star­ted or star­ted if it was not run­ning. Follow the doc­u­ment­a­tion in the Tomcat help files for the proper steps to start or re­start the server. You can then verify that Tomcat and Apache Axis are up and run­ning by fol­low­ing these steps:

                                                                                                                                                                                            在客户端计算机上打开 shell 或命令窗口并运行应用程序,如 UNIX/Linux 或 Microsoft Windows 所示:

                                                                                                                                                                                            Open a shell or com­mand window on the client ma­chine and run the ap­plic­a­tion as shown for either UNIX/Linux or Mi­crosoft Win­dows:

                                                                                                                                                                                            java -classpath %CLASSPATH% LoanQueryClient [客户 ID] [贷款金额] [贷款期限(以月为单位)]
                                                                                                                                                                                            
                                                                                                                                                                                            java -classpath %CLASSPATH% Loan­QueryC­li­ent [cus­tom­erid] [loanamount] [loan­dur­a­tion in months]
                                                                                                                                                                                            

                                                                                                                                                                                            或者

                                                                                                                                                                                            or

                                                                                                                                                                                            java -classpath $CLASSPATH LoanQueryClient [客户 ID] [贷款金额] [贷款期限(以月为单位)]
                                                                                                                                                                                            
                                                                                                                                                                                            java -classpath $CLASSPATH Loan­QueryC­li­ent [cus­tom­erid] [loanamount] [loan­dur­a­tion in months]
                                                                                                                                                                                            

                                                                                                                                                                                            例如,

                                                                                                                                                                                            For ex­ample,

                                                                                                                                                                                            java -classpath %CLASSPATH% LoanQueryClient 199 100000.00 29
                                                                                                                                                                                            
                                                                                                                                                                                            java -classpath %CLASSPATH% Loan­QueryC­li­ent 199 100000.00 29
                                                                                                                                                                                            

                                                                                                                                                                                            这将调用贷款经纪人 Web 服务并返回以下结果:

                                                                                                                                                                                            This in­vokes the loan broker Web ser­vice and re­turns the fol­low­ing res­ults:

                                                                                                                                                                                            通过 1053292919270 调用 LoanBroker Web 服务
                                                                                                                                                                                            LoanBroker 服务已回复 1053292925860 个刻度
                                                                                                                                                                                            
                                                                                                                                                                                            运行查询的总时间 = 6590 毫秒
                                                                                                                                                                                            
                                                                                                                                                                                            以下是来自贷款清算所的回复
                                                                                                                                                                                            
                                                                                                                                                                                            ssn = 199 的客户请求贷款金额 = 100000.0 29 个月
                                                                                                                                                                                            
                                                                                                                                                                                            客户的附加数据:信用评分和信用记录长度
                                                                                                                                                                                            信用评分= 756 信用记录长度= 12
                                                                                                                                                                                            
                                                                                                                                                                                            所有回复银行的最佳报价详情如下:
                                                                                                                                                                                            
                                                                                                                                                                                            总共 3 条报价中,最佳报价来自
                                                                                                                                                                                            银行名称:Exclusive Country Club Bankers
                                                                                                                                                                                            利率:6.197
                                                                                                                                                                                            
                                                                                                                                                                                            Call­ing the Loan­Broker web­ser­vice at 1053292919270 ticks
                                                                                                                                                                                            Loan­Broker ser­vice replied at 1053292925860 ticks
                                                                                                                                                                                            
                                                                                                                                                                                            Total time to run the query = 6590 mil­li­seconds
                                                                                                                                                                                            
                                                                                                                                                                                            The fol­low­ing reply was re­ceived from the Loan Clear­ing House
                                                                                                                                                                                            
                                                                                                                                                                                            Client with ssn= 199 re­quests a loan of amount= 100000.0 for 29 months
                                                                                                                                                                                            
                                                                                                                                                                                            Ad­di­tional data for cus­tomer: credit score and length of credit his­tory
                                                                                                                                                                                            Credit Score= 756 Credit His­tory Length= 12
                                                                                                                                                                                            
                                                                                                                                                                                            The de­tails of the best quote from all banks that re­spon­ded are shown below:
                                                                                                                                                                                            
                                                                                                                                                                                            Out of a total of 3 quote(s), the best quote is from
                                                                                                                                                                                            Bank Name: Ex­clus­ive Coun­try Club Bankers
                                                                                                                                                                                            In­terest Rate: 6.197
                                                                                                                                                                                            

                                                                                                                                                                                            您可以通过在运行客户端时输入不同的贷款金额来测试贷款经纪人。我们现在将分析单个客户端运行的输出结果。后来,我们推出了多个客户端。

                                                                                                                                                                                            You can test the loan broker by en­ter­ing dif­fer­ent loan amounts when run­ning the client. We will now ana­lyze the output res­ults with a single client run­ning. Later, we launch mul­tiple cli­ents.

                                                                                                                                                                                            分析输出

                                                                                                                                                                                            客户端应用程序在调用服务器上的贷款经纪人 Web 服务端点时跟踪时间。客户端还记录服务器响应结果的时间。客户端应用程序中会报告 Web 服务调用的开始时间和结束时间以及两者之间的差异。

                                                                                                                                                                                            The client ap­plic­a­tion keeps track of the time when it in­vokes the loan broker Web ser­vice en­d­point on the server. The client also notes the time when the server re­sponds with the result. Both start and end times of the call to the Web ser­vice are re­por­ted in the client ap­plic­a­tion to­gether with the dif­fer­ence between the two.

                                                                                                                                                                                            通过 1053292919270 调用 LoanBroker Web 服务
                                                                                                                                                                                            LoanBroker 服务已回复 1053292925860 个刻度
                                                                                                                                                                                            
                                                                                                                                                                                            运行查询的总时间 = 6590 毫秒
                                                                                                                                                                                            
                                                                                                                                                                                            Call­ing the Loan­Broker web­ser­vice at 1053292919270 ticks
                                                                                                                                                                                            Loan­Broker ser­vice replied at 1053292925860 ticks
                                                                                                                                                                                            
                                                                                                                                                                                            Total time to run the query = 6590 mil­li­seconds
                                                                                                                                                                                            

                                                                                                                                                                                            贷款清算所 Web 服务报告客户端应用程序通过网络发送的客户请求的所有相关详细信息。

                                                                                                                                                                                            The Loan Clear­ing House Web ser­vice re­ports all rel­ev­ant de­tails of the cus­tomer re­quest sent across the net­work by the client ap­plic­a­tion.

                                                                                                                                                                                            ssn = 199 的客户请求贷款金额 = 100000.0 29 个月
                                                                                                                                                                                            
                                                                                                                                                                                            Client with ssn= 199 re­quests a loan of amount= 100000.0 for 29 months
                                                                                                                                                                                            

                                                                                                                                                                                            贷款清算所还报告为支持客户请求而收集的其他信用数据。

                                                                                                                                                                                            The Loan Clear­ing House also re­ports ad­di­tional credit data that was gathered to sup­port the cus­tomer re­quest.

                                                                                                                                                                                            客户的附加数据:信用评分和信用记录长度
                                                                                                                                                                                            信用评分= 756 信用记录长度= 12
                                                                                                                                                                                            
                                                                                                                                                                                            Ad­di­tional data for cus­tomer: credit score and length of credit his­tory
                                                                                                                                                                                            Credit Score= 756 Credit His­tory Length= 12
                                                                                                                                                                                            

                                                                                                                                                                                            LoanBroker分析数据并选择一组符合客户贷款请求标准的银行。LoanBroker以各银行要求的格式提交客户数据,并等待各银行响应。由于这是同步应用程序,贷款经纪人会阻塞,直到银行响应或请求超时或失败。

                                                                                                                                                                                            The Loan­Broker ana­lyzes the data and se­lects a set of banks that fit the cus­tomer loan re­quest cri­teria. The Loan­Broker sub­mits the cus­tomer data in the format re­quired for in­di­vidual banks and waits for each bank to re­spond. Since this is a syn­chron­ous ap­plic­a­tion, the loan broker blocks until the bank re­sponds or the re­quest times out or fails.

                                                                                                                                                                                            贷款经纪人收集所有回复,分析返回报价,并选择最佳报价。该报价经过格式化后与任何相关数据一起发送回客户。以下是所有回复银行的最佳报价的详细信息:

                                                                                                                                                                                            The Loan­Broker col­lects all the re­sponses, ana­lyzes the return quotes, and chooses the best quote. This quote is format­ted and sent back to the cus­tomer along with any rel­ev­ant data. Here are the de­tails of the best quote from all banks that re­spon­ded:

                                                                                                                                                                                            总共 3 条报价中,最佳报价来自
                                                                                                                                                                                            银行名称:Exclusive Country Club Bankers
                                                                                                                                                                                            利率:6.197
                                                                                                                                                                                            
                                                                                                                                                                                            Out of a total of 3 quote(s), the best quote is from
                                                                                                                                                                                            Bank Name: Ex­clus­ive Coun­try Club Bankers
                                                                                                                                                                                            In­terest Rate: 6.197
                                                                                                                                                                                            

                                                                                                                                                                                            从前面的代码段中,我们看到LoanBroker报告三家银行做出了响应,并选择了最佳报价并将报价呈现给用户。

                                                                                                                                                                                            From the pre­ceed­ing code seg­ment, we see that the Loan­Broker re­ports that three banks re­spon­ded and that it se­lec­ted the best quote and presen­ted the quote to the user.

                                                                                                                                                                                            性能限制

                                                                                                                                                                                            Per­form­ance Lim­it­a­tions

                                                                                                                                                                                            正如我们在本章前面讨论序列图时所解释的,获得响应报价的总时间很长,因为贷款经纪人必须等待每家银行的响应,然后才能将请求提交给贷方列表中的下一个银行。结果,客户在提交请求后需要等待很长时间才能得到结果报价。我们使用单个客户端针对服务器运行了多次基线测试,并获得了运行查询的平均总时间(大约 8 秒)。然后,我们在同一客户端计算机上的不同窗口中启动了四个客户端实例,并获得了以下平均时间:

                                                                                                                                                                                            As ex­plained when we dis­cussed the se­quence dia­gram earlier in this chapter, the total time to get the re­sponse quote is sig­ni­fic­ant since the loan broker has to wait for a re­sponse from each bank before sub­mit­ting the re­quest to the next bank in the lenders list. As a result, the cus­tomer has to wait a long time after sub­mit­ting the re­quest to get the result quote back. We ran sev­eral baseline tests with a single client against the server and ob­tained an av­er­age of the total time to run the query (about 8 seconds). We then launched four in­stances of the client in sep­ar­ate win­dows on the same client ma­chine and ob­tained the fol­low­ing av­er­age times:

                                                                                                                                                                                            客户端1:12520毫秒
                                                                                                                                                                                            客户端2:12580毫秒
                                                                                                                                                                                            客户端3:15710毫秒
                                                                                                                                                                                            客户端4:13760毫秒
                                                                                                                                                                                            
                                                                                                                                                                                            Client 1: 12520 mil­li­seconds
                                                                                                                                                                                            Client 2: 12580 mil­li­seconds
                                                                                                                                                                                            Client 3: 15710 mil­li­seconds
                                                                                                                                                                                            Client 4: 13760 mil­li­seconds
                                                                                                                                                                                            

                                                                                                                                                                                            虽然这些测试无论如何都不能被认为是科学的,但经验证据表明,当多个客户尝试同时访问我们的贷款经纪人系统时,性能会受到巨大影响。

                                                                                                                                                                                            While these tests could not be con­sidered sci­entific by any stretch of ima­gin­a­tion, the em­pir­ical evid­ence points to the fact that per­form­ance suf­fers tre­mend­ously when mul­tiple cli­ents try to sim­ul­tan­eously access our loan broker system.

                                                                                                                                                                                            本例的局限性

                                                                                                                                                                                            Lim­it­a­tions of This Ex­ample

                                                                                                                                                                                            为了使贷款经纪人示例的设计讨论更加容易,我们选择将所有 Web 服务实现为 JWS 文件。这为我们提供了通过简单地将服务复制到服务器来部署服务的优势。然而,缺点是服务类的新实例会为每个请求实例化,并在请求完成后立即释放。这意味着我们注意到的一些时间延迟是服务器在调用服务之前创建该类的实例的结果。

                                                                                                                                                                                            To make the dis­cus­sion of the design of the loan broker ex­ample easier, we chose to im­ple­ment all the Web ser­vices as JWS files. This gave us the ad­vant­age of de­ploy­ing the ser­vice by simply copy­ing it over to the server. The dis­ad­vant­age, how­ever, is that a new in­stance of the ser­vice class is in­stan­ti­ated for each re­quest and is deal­loc­ated as soon as the re­quest is com­plete. This means that some amount of the time lag we no­ticed is a result of the server cre­at­ing in­stances of the class before in­vok­ing the ser­vice.

                                                                                                                                                                                            我们可以选择更复杂的路线并设计一个将使用 WSDD 文件进行部署的 Java 类文件。这将使我们能够灵活地定义实例化类的持续时间:整个客户端会话或应用程序的持续时间。在设计实际应用程序时,如何部署 Web 服务是一个重要的问题。如果我们选择包含部署问题,则示例的描述将变得非常冗长,并且对于本章的目的而言,设计细节将变得不必要的复杂。

                                                                                                                                                                                            We could have chosen the more com­plic­ated route and de­signed a Java class file that would get de­ployed using a WSDD file. This would give us the flex­ib­il­ity of de­fin­ing how long the in­stan­ti­ated class would per­sist: for the entire client ses­sion or for the dur­a­tion of the ap­plic­a­tion. The issue of how to deploy a Web ser­vice is a sig­ni­fic­ant one when design­ing a real-world ap­plic­a­tion. If we had chosen to in­clude the de­ploy­ment issue, the de­scrip­tion of the ex­ample would have become very lengthy, and the design de­tails would have become un­ne­ces­sar­ily com­plic­ated for the pur­poses of this chapter.

                                                                                                                                                                                            概括

                                                                                                                                                                                            Sum­mary

                                                                                                                                                                                            在本节中,我们逐步完成了使用同步 SOAP/HTTP Web 服务的贷款经纪人应用程序的实现。我们使用预测路由将贷款请求提交给一组银行。我们强调了使用这种方法的优点和缺点。我们做了一些设计权衡来管理讨论并避免陷入描述部署细节的困境。总体目的是就这种方法的优点和缺点进行讨论。我们还看到了本书中描述的许多模式的使用,这将有助于将同步预测方法应用于其他业务领域。

                                                                                                                                                                                            In this sec­tion we stepped through the im­ple­ment­a­tion of the loan broker ap­plic­a­tion using syn­chron­ous SOAP/HTTP Web ser­vices. We used pre­dict­ive rout­ing to submit our loan re­quest to a set of banks. We high­lighted the strengths and draw­backs of using this ap­proach. We made some design trade-offs to manage the dis­cus­sion and avoid get­ting bogged down in de­scrib­ing de­ploy­ment de­tails. The over­all in­ten­tion was to provide a dis­cus­sion on the merits and de­mer­its of this ap­proach. We also saw the use of many of the pat­terns de­scribed in this book, which will help in ad­apt­ing the syn­chron­ous pre­dict­ive ap­proach to other busi­ness do­mains.

                                                                                                                                                                                              MSMQ 异步实现

                                                                                                                                                                                              Asynchronous Implementation with MSMQ

                                                                                                                                                                                              本节介绍如何使用 Microsoft .NET、C# 和 MSMQ 实现贷款经纪人示例(请参阅本章的简介)。Microsoft .NET 框架包括System.Messaging命名空间,该命名空间使 .NET 程序能够访问最新版本的 Windows 操作系统(Windows 2000、Windows XP 和 Windows Server 2003)中包含的 Microsoft 消息队列服务。该示例演示了许多设计决策,并显示​​了使该解决方案发挥作用所需的实际代码。我们尽可能关注解决方案的设计方面,因此即使您不是核心 C# 开发人员,此示例也很有价值。事实上,除了System.Messaging 的实际接口之外,许多应用程序如果这个实现是用 Java 和 JMS 完成的,那么看起来会非常相似。

                                                                                                                                                                                              This sec­tion de­scribes how to im­ple­ment the loan broker ex­ample (see the in­tro­duc­tion to this chapter) using Mi­crosoft .NET, C#, and MSMQ. The Mi­crosoft .NET frame­work in­cludes the System.Mes­saging namespace that gives .NET pro­grams access to the Mi­crosoft Mes­sage Queuing Ser­vice in­cluded in the recent ver­sions of the Win­dows op­er­at­ing system (Win­dows 2000, Win­dows XP, and Win­dows Server 2003). The ex­ample walks through many of the design de­cisions and shows the actual code re­quired to make this solu­tion work. As much as pos­sible, we focus on the design as­pects of the solu­tion so that this ex­ample is of value even if you are not a hard-core C# de­ve­loper. In fact, much of the ap­plic­a­tion be­sides the actual in­ter­face into System.Mes­saging would look very sim­ilar if this im­ple­ment­a­tion were done in Java and JMS.

                                                                                                                                                                                              通过使用 Microsoft BizTalk Server 等集成和编排工具,可以用更少的编码工作来实现此解决方案中演示的一些功能。我有意避免使用此类工具有两个原因。首先,这些工具不是免费的,您必须获得许可证才能运行这个简单的示例。其次,我想演示所有必要功能的显式实现。

                                                                                                                                                                                              Some of the func­tions demon­strated in this solu­tion could likely be im­ple­men­ted with less coding effort by using an in­teg­ra­tion and or­ches­tra­tion tool such as Mi­crosoft BizTalk Server. I in­ten­tion­ally avoided using such tools for two reas­ons. First, these tools are not free, and you would have to ac­quire a li­cense just to run this simple ex­ample. Second, I wanted to demon­strate the ex­pli­cit im­ple­ment­a­tion of all ne­ces­sary func­tions.

                                                                                                                                                                                              该解决方案被设置为多个可执行文件,以便不同的组件可以分布在多台计算机上。出于示例的目的,使用本地私人消息来保持设置要求简单并避免安装 Active Directory。因此,解决方案“按原样”必须在单台计算机上运行。

                                                                                                                                                                                              The solu­tion is set up as mul­tiple ex­ecut­ables so that the dif­fer­ent com­pon­ents can be dis­trib­uted across mul­tiple com­puters. For pur­pose of the ex­ample, local, private mes­sages are used to keep the setup re­quire­ments simple and avoid having to in­stall Active Dir­ect­ory. As a result, the solu­tion "as is" must run on a single ma­chine.

                                                                                                                                                                                              贷款经纪人示例的此实现使用消息队列上的异步消息传递。如示例概述中所述,这允许我们同时处理多个报价请求,但也要求我们在消息流经系统时将其关联起来,并最终生成贷款报价请求的响应消息。在本示例中,我们的许多设计决策都是由异步处理的需求驱动的。

                                                                                                                                                                                              This im­ple­ment­a­tion of the loan broker ex­ample uses asyn­chron­ous mes­saging over mes­sage queues. As de­scribed in the ex­ample over­view, this allows us to pro­cess mul­tiple quote re­quests con­cur­rently but also re­quires us to cor­rel­ate mes­sages as they flow though the system and ul­ti­mately pro­duce a re­sponse mes­sage to the loan quote re­quest. Many of our design de­cisions over the course of this ex­ample are driven by the need for asyn­chron­ous pro­cess­ing.

                                                                                                                                                                                              贷款经纪人生态系统

                                                                                                                                                                                              Loan Broker Eco­sys­tem

                                                                                                                                                                                              从外到内开始理解贷款经纪人的设计是个好主意。让我们首先检查贷款经纪人必须支持的所有外部接口(见图)。因为消息队列是单向的,所以我们需要一对队列来与另一个组件建立请求-响应通信(有关简单情况,请参阅第 6 章“插曲:简单消息传递”中的“.NET 请求/回复示例”部分)结果,贷款经纪人在贷款请求队列上收到贷款报价请求,并在 上回复测试客户端loanReplyQueue。与信用局的交互发生在一对类似的队列上。不是为每个银行创建一对队列,银行回复队列。接收者列表将请求消息发送到每个单独的银行队列,而聚合器从到达 LoanReplyQueue 的回复消息中选择最佳报价。 接收者列表和聚合器一起充当分发式分散-聚集。为了简单起见,本例中的所有银行都使用相同的消息格式,以便规范化器不需要。但由于常见的银行消息格式与消费者期望的格式不同,我们仍然需要使用一个消息转换器将银行回复消息转换为贷款经纪人回复消息。我决定将贷款经纪人设计为流程经理。贷款经纪人不是将贷款经纪人内部的功能实现为由消息队列分隔的单独组件,而是在内部执行所有功能的单个组件。这种方法消除了在这些功能之间跨队列发送消息所产生的开销,但它要求贷款经纪人维护多个并发流程实例。

                                                                                                                                                                                              It is a good idea to start un­der­stand­ing the loan broker design from the out­side in. Let's start by ex­amin­ing all ex­ternal in­ter­faces that the loan broker has to sup­port (see figure). Be­cause mes­sage queues are uni­direc­tional, we need a pair of queues to es­tab­lish a re­quest-re­sponse com­mu­nic­a­tion with an­other com­pon­ent (see sec­tion ".NET Re­quest/Reply Ex­ample" in Chapter 6, "In­ter­lude: Simple Mes­saging," for a simple case). As a result, the loan broker re­ceives re­quests for loan quotes on the loan­Re­questQueue and replies to the test client on the loan­ReplyQueue. The in­ter­ac­tion with the credit bureau hap­pens over a sim­ilar pair of queues. Rather than create a pair of queues for each bank, we de­cided to have all banks reply to the same bankReplyQueue. The Re­cip­i­ent List sends the re­quest mes­sage to each in­di­vidual bank queue, while the Ag­greg­ator se­lects the best quote from the reply mes­sages ar­riv­ing on the loan­ReplyQueue. To­gether, the Re­cip­i­ent List and the Ag­greg­ator act as a dis­tri­bu­tion-style Scat­ter-Gather. For sim­pli­city's sake, all banks in this ex­ample use the same mes­sage format so that a Nor­mal­izer is not re­quired. But be­cause the common bank mes­sage format is dif­fer­ent from the format ex­pec­ted by the con­sumer, we still need to use one Mes­sage Trans­lator to con­vert bank reply mes­sages into a loan broker reply mes­sage. I de­cided to design the loan broker as a Pro­cess Man­ager. Rather than im­ple­ment­ing the func­tions inside the loan broker as in­di­vidual com­pon­ents sep­ar­ated by mes­sage queues, the loan broker is a single com­pon­ent that ex­ecutes all func­tions in­tern­ally. This ap­proach elim­in­ates the over­head that would be in­curred by send­ing mes­sages across queues between these func­tions, but it re­quires the loan broker to main­tain mul­tiple, con­cur­rent pro­cess in­stances.

                                                                                                                                                                                              具有消息队列接口的贷款经纪人

                                                                                                                                                                                              Loan Broker with Mes­sage Queue In­ter­faces

                                                                                                                                                                                              图形/09inf12.gif

                                                                                                                                                                                              奠定基础:消息传递网关

                                                                                                                                                                                              Laying the Ground­work: A Mes­saging Gate­way

                                                                                                                                                                                              本节并不是对System.Messaging命名空间和 MSMQ的介绍。因此,将 MSMQ 特定的函数分成单独的类是有意义的,这样应用程序代码就不会充斥着 MSMQ 特定的命令。网关 [ EAA ]是用于此目的的绝佳模式,并提供两个关键优势:首先,它从应用程序中抽象了通信的技术细节。其次,如果我们选择将网关接口与网关实现分离,我们可以用Service Stub [ EAA ] 替换实际的外部服务进行测试。

                                                                                                                                                                                              This sec­tion is not meant as an in­tro­duc­tion into the System.Mes­saging namespace and MSMQ. There­fore, it makes sense to sep­ar­ate the MSMQ-spe­cific func­tions into sep­ar­ate classes so that the ap­plic­a­tion code will not be littered with MSMQ-spe­cific com­mands. Gate­way [EAA] is an ex­cel­lent pat­tern to use for this pur­pose and provides two key ad­vant­ages: First, it ab­stracts the tech­nical de­tails of the com­mu­nic­a­tion from the ap­plic­a­tion. Second, if we choose to sep­ar­ate the gate­way in­ter­face from the gate­way im­ple­ment­a­tion, we can re­place the actual ex­ternal ser­vice with a Ser­vice Stub [EAA] for test­ing.

                                                                                                                                                                                              网关有助于将 MSMQ 详细信息排除在应用程序之外并提高可测试性

                                                                                                                                                                                              A Gate­way Helps Keep MSMQ De­tails Out of the Ap­plic­a­tion and Im­proves Test­abil­ity

                                                                                                                                                                                              图形/09inf13.gif

                                                                                                                                                                                              在我们的例子中,我们在消息网关中定义了两个接口: IMessageSenderIMessageReceiver 。我们让这些界面变得非常简单。IMessageSender能做的就是发送消息,而IMessageReceiver 能做的就是(惊讶!)接收消息。此外,接收方还有一个Begin方法来告诉它可以开始接收消息了。保持接口如此简单使得定义实现该接口的类变得容易。

                                                                                                                                                                                              In our case, we define two in­ter­faces into the Mes­saging Gate­way : IMes­sageSender and IMes­sageRe­ceiver. We kept these in­ter­faces almost trivi­ally simplistic. All the IMes­sageSender can do is send a mes­sage, and all the IMes­sageRe­ceiver can do is (sur­prise!) re­ceive a mes­sage. Ad­di­tion­ally, the re­ceiver has a Begin method to tell it that it is okay to start re­ceiv­ing mes­sages. Keep­ing the in­ter­faces this simple makes it easy to define classes that im­ple­ment the in­ter­face.

                                                                                                                                                                                              IMessageSender.cs
                                                                                                                                                                                              命名空间消息网关
                                                                                                                                                                                              {
                                                                                                                                                                                                  使用系统消息传递;
                                                                                                                                                                                              
                                                                                                                                                                                                  公共接口 IMessageSender
                                                                                                                                                                                                  {
                                                                                                                                                                                                      void Send(消息混乱);
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              namespace Mes­sageG­ate­way
                                                                                                                                                                                              {
                                                                                                                                                                                                  using System.Mes­saging;
                                                                                                                                                                                              
                                                                                                                                                                                                  public in­ter­face IMes­sageSender
                                                                                                                                                                                                  {
                                                                                                                                                                                                      void Send(Mes­sage mess);
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              IMessageReceiver.cs
                                                                                                                                                                                              命名空间消息网关
                                                                                                                                                                                              {
                                                                                                                                                                                                  使用系统消息传递;
                                                                                                                                                                                              
                                                                                                                                                                                                  公共接口 IMessageReceiver
                                                                                                                                                                                                  {
                                                                                                                                                                                                      OnMsgEvent On消息
                                                                                                                                                                                                      {
                                                                                                                                                                                                          得到;
                                                                                                                                                                                                          放;
                                                                                                                                                                                                      }
                                                                                                                                                                                              
                                                                                                                                                                                                      无效开始();
                                                                                                                                                                                              
                                                                                                                                                                                                      消息队列 GetQueue();
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              namespace Mes­sageG­ate­way
                                                                                                                                                                                              {
                                                                                                                                                                                                  using System.Mes­saging;
                                                                                                                                                                                              
                                                                                                                                                                                                  public in­ter­face IMes­sageRe­ceiver
                                                                                                                                                                                                  {
                                                                                                                                                                                                      OnMs­gEvent On­Mes­sage
                                                                                                                                                                                                      {
                                                                                                                                                                                                          get;
                                                                                                                                                                                                          set;
                                                                                                                                                                                                      }
                                                                                                                                                                                              
                                                                                                                                                                                                      void Begin();
                                                                                                                                                                                              
                                                                                                                                                                                                      Mes­sageQueue GetQueue();
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              实际的实现位于MessageSenderGateway 和MessageReceiverGateway类中。这些类负责配置消息队列属性,例如MessageReadPropertyFilter 或 Formatter设置。 MessageReceiverGateway 对MSMQ消息队列的ReceiveCompleted使用模板方法[GoF]来处理小而重要的细节,例如处理。我们不会深入探讨这些功能的细节,而是建议您参考 MSDN 上的在线文档 [MSMQ ]。

                                                                                                                                                                                              The actual im­ple­ment­a­tions reside in the Mes­sageSender­Gate­way and Mes­sageRe­ceiv­er­Gate­way classes. These classes take care of con­fig­ur­ing the mes­sage queue prop­er­ties, such as Mes­sageRead­Prop­er­ty­Fil­ter or Format­ter set­tings. Mes­sageRe­ceiv­er­Gate­way uses a Tem­plate Method [GoF] for the Re­ceive­Com­pleted event of the MSMQ mes­sage queue to take care of small but im­port­ant de­tails, such as call­ing the mq.Be­gin­Re­ceive method after pro­cess­ing the mes­sage. In­stead of diving into the de­tails of these fea­tures, we refer you to the online doc­u­ment­a­tion on MSDN [MSMQ].

                                                                                                                                                                                              因为我们定义了非常狭窄的接口,所以也可以提供甚至不使用消息队列的实现。MockQueue实现了这两个接口,甚至没有引用消息队列!当应用程序发送消息时,MockQueue立即使用同一消息触发OnMessage 事件。这使得在单个地址空间中测试应用程序变得更加简单,而不必担心异步方面(更多关于测试的内容如下)。

                                                                                                                                                                                              Be­cause we defined very narrow in­ter­faces, it is also pos­sible to provide an im­ple­ment­a­tion that does not even use a mes­sage queue. Mock­Queue im­ple­ments both in­ter­faces without even ref­er­en­cing a mes­sage queue! When an ap­plic­a­tion sends a mes­sage, Mock­Queue im­me­di­ately trig­gers the On­Mes­sage event with that same mes­sage. This makes test­ing the ap­plic­a­tion in a single ad­dress space much sim­pler without having to worry about the asyn­chron­ous as­pects (more on test­ing fol­lows).

                                                                                                                                                                                              对于 C# 新手来说,IMessageReceiver中的 OnMsgEvent OnMessage行可能需要一些解释。.NET 框架为观察者模式[ GoF]提供了语言功能,称为委托和事件。 OnMsgEvent 是MessageReceiverGateway中定义的委托:

                                                                                                                                                                                              The line OnMs­gEvent On­Mes­sage in IMes­sageRe­ceiver may re­quire a little bit of ex­plan­a­tion for those who are new to C#. The .NET frame­work provides lan­guage fea­tures for the Ob­server pat­tern [GoF], called del­eg­ates and events. OnMs­gEvent is a del­eg­ate defined in the Mes­sageRe­ceiv­er­Gate­way:

                                                                                                                                                                                              公共委托 void OnMsgEvent(Message msg);
                                                                                                                                                                                              
                                                                                                                                                                                              public del­eg­ate void OnMs­gEvent(Mes­sage msg);
                                                                                                                                                                                              

                                                                                                                                                                                              委托允许对象注册某种类型的事件。当事件被调用时,.NET 调用所有已注册的方法。可以通过多种方式调用委托,但最简单的形式是使用委托名称直接调用:

                                                                                                                                                                                              A del­eg­ate allows ob­jects to re­gister with a cer­tain type of event. When the event is in­voked, .NET calls all re­gistered meth­ods. A del­eg­ate can be in­voked in a number of ways, but the simplest form is the direct in­voc­a­tion by using the name of the del­eg­ate:

                                                                                                                                                                                              OnMsgEvent 接收器;
                                                                                                                                                                                              留言留言;
                                                                                                                                                                                              ...
                                                                                                                                                                                              接收者(消息);
                                                                                                                                                                                              
                                                                                                                                                                                              OnMs­gEvent re­ceiver;
                                                                                                                                                                                              Mes­sage mes­sage;
                                                                                                                                                                                              ...
                                                                                                                                                                                              re­ceiver(mes­sage);
                                                                                                                                                                                              

                                                                                                                                                                                              如果这让您对委托更感兴趣,请看一本好的 .NET 或 C# 书籍。如果您想了解公共语言运行时 (CLR) 如何实现它们的详细细节,请查看 [ Box ]。

                                                                                                                                                                                              If this leaves you more in­ter­ested in del­eg­ates, have a look at a good .NET or C# book. If you want to know the dirty de­tails on how the Common Lan­guage Runtime (CLR) im­ple­ments them, have a look at [Box].

                                                                                                                                                                                              通用功能的基类

                                                                                                                                                                                              Base Classes for Common Func­tion­al­ity

                                                                                                                                                                                              当我们查看高层设计时,我们很快意识到贷款经纪人场景中的一些组件具有通用功能。例如,银行和征信机构都充当服务,接收请求、处理请求并将结果发布到另一个渠道。听起来很容易。但由于我们生活在异步消息传递的世界中,因此我们必须做一些额外的工作来实现即使是简单的请求-答复方案。首先,我们希望服务的调用者指定回复的返回地址。这允许不同的呼叫者使用相同的服务但​​使用不同的回复队列。该服务还应该支持相关标识符以便调用者可以协调传入的回复消息与请求消息。此外,如果服务接收到无法识别的格式的消息,最好将该消息路由到无效消息通道而不是简单地丢弃它。

                                                                                                                                                                                              When we look at the high-level design, we quickly real­ize that some of the com­pon­ents in the loan broker scen­ario have common func­tions. For ex­ample, both a bank and a credit bureau act as a ser­vice by re­ceiv­ing a re­quest, pro­cess­ing the re­quest, and pub­lish­ing the result to an­other chan­nel. Sounds easy enough. But since we live in the world of asyn­chron­ous mes­saging, we have to do a little extra work to im­ple­ment even a simple re­quest-reply scheme. First, we want the caller of the ser­vice to spe­cify a Return Ad­dress for the reply. This allows dif­fer­ent callers to use the same ser­vice but use dif­fer­ent reply queues. The ser­vice should also sup­port a Cor­rel­a­tion Iden­ti­fier so that the caller can re­con­cile in­com­ing reply mes­sages with re­quest mes­sages. Fur­ther­more, if the ser­vice re­ceives a mes­sage in an un­re­cog­nized format, it would be good man­ners to route the mes­sage to an In­valid Mes­sage Chan­nel in­stead of simply dis­card­ing it.

                                                                                                                                                                                              为了消除代码重复(面向对象编程的致命罪过),我创建了基类MQService 。此类包含对返回地址相关标识符的支持。实际上,服务器端对相关标识符的支持只不过是将传入消息的消息 ID 复制到回复消息的相关 ID。在我们的示例中,我们还复制了AppSpecific属性,因为稍后我们会看到,有时我们需要通过消息 ID 以外的属性进行关联。MQService还确保将响应发送到指定的返回地址。因为请求者提供Return Address ,所以MQService的唯一初始化参数是请求队列的名称(新请求消息进入的队列)。如果请求者忘记提供Return Address , RequestReplyService会将回复到无效消息通道。我们还可以考虑将请求消息发送到无效消息通道,因为这是导致故障的消息。现在,我们将保持简单,不涉及错误处理的细节。

                                                                                                                                                                                              To elim­in­ate code du­plic­a­tion (the deadly sin of object-ori­ented pro­gram­ming), I create the base class MQSer­vice. This class in­cor­por­ates sup­port for Return Ad­dress and Cor­rel­a­tion Iden­ti­fier. Really, the server-side sup­port for a Cor­rel­a­tion Iden­ti­fier con­sists of noth­ing more than copy­ing the mes­sage ID of the in­com­ing mes­sage to the cor­rel­a­tion ID of the reply mes­sage. In our ex­ample, we also copy the AppSpe­cific prop­erty be­cause we will see later that some­times we need to cor­rel­ate by a prop­erty other than the mes­sage ID. The MQSer­vice also makes sure to send the re­sponse to the spe­cified Return Ad­dress. Be­cause the re­questor sup­plies the Return Ad­dress , the only ini­tial­iz­a­tion para­meter for the MQSer­vice is the name of the re­quest queuethe queue where new re­quest mes­sages come in. If the re­questor for­gets to supply a Return Ad­dress , the Re­questReplyS­er­vice sends the reply to the In­valid Mes­sage Chan­nel. We may also con­sider send­ing the re­quest mes­sage to the In­valid Mes­sage Chan­nel be­cause that's the mes­sage that caused the fault. For now, we will keep our lives simple and not get into the de­tails of error hand­ling.

                                                                                                                                                                                              MQService.cs
                                                                                                                                                                                              公共抽象类 MQService
                                                                                                                                                                                              {
                                                                                                                                                                                                  静态受保护只读字符串 InvalidMessageQueueName =
                                                                                                                                                                                                                                   ".\\private$\\invalidMessageQueue";
                                                                                                                                                                                                  IMessageSender invalidQueue = new MessageSenderGateway(InvalidMessageQueueName);
                                                                                                                                                                                              
                                                                                                                                                                                                  受保护的 IMessageReceiver 请求队列;
                                                                                                                                                                                                  受保护类型 requestBodyType;
                                                                                                                                                                                              
                                                                                                                                                                                                  公共MQService(IMessageReceiver接收器)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      请求队列=接收者;
                                                                                                                                                                                                      注册(请求队列);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  公共 MQService(字符串请求队列名称)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      MessageReceiverGateway q = new MessageReceiverGateway(requestQueueName,
                                                                                                                                                                                                                                                            获取格式化程序());
                                                                                                                                                                                                      注册(q);
                                                                                                                                                                                                      this.requestQueue = q;
                                                                                                                                                                                                      Console.WriteLine("正在处理来自 " + requestQueueName 的消息);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  受保护的虚拟 IMessageFormatter GetFormatter()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      返回新的 XmlMessageFormatter(new Type[] { GetRequestBodyType() });
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  受保护的抽象类型 GetRequestBodyType();
                                                                                                                                                                                              
                                                                                                                                                                                                  受保护对象 GetTypedMessageBody(消息 msg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      尝试
                                                                                                                                                                                                      {
                                                                                                                                                                                                          if (msg.Body.GetType().Equals(GetRequestBodyType()))
                                                                                                                                                                                                          {
                                                                                                                                                                                                              返回消息正文;
                                                                                                                                                                                                          }
                                                                                                                                                                                                          别的
                                                                                                                                                                                                          {
                                                                                                                                                                                                              Console.WriteLine("消息格式非法。");
                                                                                                                                                                                                              返回空值;
                                                                                                                                                                                                          }
                                                                                                                                                                                                      }
                                                                                                                                                                                                      捕获(异常 e)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          Console.WriteLine("消息格式非法" + e.Message);
                                                                                                                                                                                                          返回空值;
                                                                                                                                                                                                      }
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  公共无效注册(IMessageReceiver记录)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      OnMsgEvent ev = new OnMsgEvent(OnMessage);
                                                                                                                                                                                                      rec.OnMessage += ev;
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  公共无效运行()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      requestQueue.Begin();
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                              
                                                                                                                                                                                                  公共无效SendReply(对象outObj,消息inMsg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      消息 outMsg = 新消息(outObj);
                                                                                                                                                                                                      outMsg.CorrelationId = inMsg.Id;
                                                                                                                                                                                                      outMsg.AppSpecific = inMsg.AppSpecific;
                                                                                                                                                                                              
                                                                                                                                                                                                      if (inMsg.ResponseQueue != null)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          IMessageSender 回复队列 = new MessageSenderGateway(inMsg.ResponseQueue);
                                                                                                                                                                                                          回复队列.Send(outMsg);
                                                                                                                                                                                                      }
                                                                                                                                                                                                      别的
                                                                                                                                                                                                      {
                                                                                                                                                                                                          invalidQueue.Send(outMsg);
                                                                                                                                                                                                      }
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  protected 摘要 void OnMessage(Message inMsg);
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              public ab­stract class MQSer­vice
                                                                                                                                                                                              {
                                                                                                                                                                                                  static pro­tec­ted readonly String In­val­idMes­sageQueueName =
                                                                                                                                                                                                                                   ".\\private$\\in­val­idMes­sageQueue";
                                                                                                                                                                                                  IMes­sageSender in­val­idQueue = new Mes­sageSender­Gate­way(In­val­idMes­sageQueueName);
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted IMes­sageRe­ceiver re­questQueue;
                                                                                                                                                                                                  pro­tec­ted Type re­quest­Bo­dy­Type;
                                                                                                                                                                                              
                                                                                                                                                                                                  public MQSer­vice(IMes­sageRe­ceiver re­ceiver)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      re­questQueue = re­ceiver;
                                                                                                                                                                                                      Re­gister(re­questQueue);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  public MQSer­vice(String re­questQueueName)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      Mes­sageRe­ceiv­er­Gate­way q = new Mes­sageRe­ceiv­er­Gate­way(re­questQueueName,
                                                                                                                                                                                                                                                            Get­Format­ter());
                                                                                                                                                                                                      Re­gister(q);
                                                                                                                                                                                                      this.re­questQueue = q;
                                                                                                                                                                                                      Con­sole.WriteLine("Pro­cess­ing mes­sages from " + re­questQueueName);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted vir­tual IMes­sage­Format­ter Get­Format­ter()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      return new Xm­lMes­sage­Format­ter(new Type[] { GetRe­quest­Bo­dy­Type() });
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted ab­stract Type GetRe­quest­Bo­dy­Type();
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted Object Get­TypedMes­sage­Body(Mes­sage msg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      try
                                                                                                                                                                                                      {
                                                                                                                                                                                                          if (msg.Body.Get­Type().Equals(GetRe­quest­Bo­dy­Type()))
                                                                                                                                                                                                          {
                                                                                                                                                                                                              return msg.Body;
                                                                                                                                                                                                          }
                                                                                                                                                                                                          else
                                                                                                                                                                                                          {
                                                                                                                                                                                                              Con­sole.WriteLine("Il­legal mes­sage format.");
                                                                                                                                                                                                              return null;
                                                                                                                                                                                                          }
                                                                                                                                                                                                      }
                                                                                                                                                                                                      catch (Ex­cep­tion e)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          Con­sole.WriteLine("Il­legal mes­sage format" + e.Mes­sage);
                                                                                                                                                                                                          return null;
                                                                                                                                                                                                      }
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  public void Re­gister(IMes­sageRe­ceiver rec)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      OnMs­gEvent ev = new OnMs­gEvent(On­Mes­sage);
                                                                                                                                                                                                      rec.On­Mes­sage += ev;
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  public void Run()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      re­questQueue.Begin();
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                              
                                                                                                                                                                                                  public void SendReply(Object outObj, Mes­sage inMsg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      Mes­sage outMsg = new Mes­sage(outObj);
                                                                                                                                                                                                      outMsg.Cor­rel­a­tionId = inMsg.Id;
                                                                                                                                                                                                      outMsg.AppSpe­cific = inMsg.AppSpe­cific;
                                                                                                                                                                                              
                                                                                                                                                                                                      if (inMsg.Re­spon­se­Queue != null)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          IMes­sageSender replyQueue = new Mes­sageSender­Gate­way(inMsg.Re­spon­se­Queue);
                                                                                                                                                                                                          replyQueue.Send(outMsg);
                                                                                                                                                                                                      }
                                                                                                                                                                                                      else
                                                                                                                                                                                                      {
                                                                                                                                                                                                          in­val­idQueue.Send(outMsg);
                                                                                                                                                                                                      }
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted ab­stract void On­Mes­sage(Mes­sage inMsg);
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              该类是抽象的,因为它不提供GetTypedMessageBody 和OnMessage方法的实现。因为我们希望我们的类尽可能多地处理强类型业务对象而不是消息数据类型,所以我们让 MQService验证消息正文的类型并将其转换为正确的类型。问题是这个抽象基类不知道将其转换为哪种类型,因为该基类可以由许多不同的服务实现使用,每个服务实现都可能使用不同的消息类型。为了在基类中执行尽可能多的工作,我们创建了方法GetTypedMessageBody和抽象方法获取请求正文类型。每个子类都必须实现 GetRequestBodyType 方法指定它期望接收的消息的类型。MQServer使用该类型来初始化 XML 格式化程序并执行类型检查。经过这些检查后,子类可以安全地将传入的消息正文转换为所需的类型,而不会导致异常。GetTypedMessageBody内部的异常处理在这一点上无疑是原始的,它所做的只是将消息打印到控制台。如果这不是一个简单的演示应用程序,我们肯定会使用更复杂的日志记录方法,或者更好的是,使用全面的控制总线

                                                                                                                                                                                              The class is ab­stract be­cause it does not provide an im­ple­ment­a­tion for the Get­TypedMes­sage­Body and On­Mes­sage meth­ods. Be­cause we want our classes to deal as much as pos­sible with strongly typed busi­ness ob­jects as op­posed to Mes­sage data types, we have the MQSer­vice verify the type of the mes­sage body and cast it to the cor­rect type. The prob­lem is that this ab­stract base class does not know which type to cast it to be­cause the base class can be used by many dif­fer­ent ser­vice im­ple­ment­a­tions, each of which is likely to use a dif­fer­ent mes­sage type. To per­form as much work as pos­sible in the base class, we cre­ated the method Get­TypedMes­sage­Body and the ab­stract method GetRe­quest­Bo­dy­Type. Each sub­class has to im­ple­ment the method GetRe­quest­Bo­dy­Type to spe­cify the type of the mes­sages that it ex­pects to re­ceive. MQServer uses the type to ini­tial­ize the XML format­ter and to per­form type check­ing. After these checks, the sub­class can safely cast the in­com­ing mes­sage body to the de­sired type without caus­ing ex­cep­tions. The ex­cep­tion hand­ling inside Get­TypedMes­sage­Body is ad­mit­tedly prim­it­ive at this point­all it does is print a mes­sage to the con­sole. If this weren't a simple demo app, we would def­in­itely use a more soph­ist­ic­ated ap­proach to log­ging or, better yet, a com­pre­hens­ive Con­trol Bus.

                                                                                                                                                                                              OnMessage方法MQService 的子类来实现。我们提供两种实现,一种是同步的,一种是异步的。同步实现 ( RequestReplyService )调用虚拟方法ProcessMessage ,该方法预计返回回复消息,并立即调用 SendReply 。相反,异步实现 ( AsyncRequestReplyService ) 定义了没有任何返回参数的虚拟 ProcessMessage 方法。继承子类负责调用SendReply

                                                                                                                                                                                              The On­Mes­sage method is left to be im­ple­men­ted by the sub­classes of MQSer­vice. We provide two im­ple­ment­a­tions, a syn­chron­ous one and an asyn­chron­ous one. The syn­chron­ous im­ple­ment­a­tion (Re­questReplyS­er­vice) calls the vir­tual method Pro­cess­Mes­sage, which is ex­pec­ted to return a reply mes­sage, and calls SendReply right away. The asyn­chron­ous im­ple­ment­a­tion (Asyn­cRe­questReplyS­er­vice), in con­trast, defines the vir­tual Pro­cess­Mes­sage method without any return para­meter. The in­her­it­ing sub­classes are re­spons­ible for call­ing SendReply.

                                                                                                                                                                                              MQService.cs
                                                                                                                                                                                              公共类RequestReplyService:MQService
                                                                                                                                                                                              {
                                                                                                                                                                                                  公共RequestReplyService(IMessageReceiver接收器):基(接收器){}
                                                                                                                                                                                                  公共RequestReplyService(字符串请求队列名称):基(请求队列名称){}
                                                                                                                                                                                              
                                                                                                                                                                                                  受保护的覆盖类型 GetRequestBodyType()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      返回 typeof(System.String);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  受保护的虚拟对象 ProcessMessage(Object o)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      字符串体 = (String)o;
                                                                                                                                                                                                      Console.WriteLine("收到消息:" + body);
                                                                                                                                                                                                      返回主体;
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  protected override void OnMessage(Message inMsg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      inMsg.Formatter = GetFormatter();
                                                                                                                                                                                                      对象 inBody = GetTypedMessageBody(inMsg);
                                                                                                                                                                                                      if (inBody!= null)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          对象 outBody = ProcessMessage(inBody);
                                                                                                                                                                                              
                                                                                                                                                                                                          if (outBody!= null)
                                                                                                                                                                                                          {
                                                                                                                                                                                                              SendReply(outBody, inMsg);
                                                                                                                                                                                                          }
                                                                                                                                                                                                      }
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              公共类 AsyncRequestReplyService :MQService
                                                                                                                                                                                              {
                                                                                                                                                                                                  公共 AsyncRequestReplyService(IMessageReceiver 接收器):基(接收器){}
                                                                                                                                                                                                  公共 AsyncRequestReplyService(String requestQueueName) : base (requestQueueName) {}
                                                                                                                                                                                              
                                                                                                                                                                                              
                                                                                                                                                                                                  受保护的覆盖类型 GetRequestBodyType()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      返回 typeof(System.String);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  protected virtual void ProcessMessage(Object o, Message msg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      字符串体 = (String)o;
                                                                                                                                                                                                      Console.WriteLine("收到消息:" + body);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  protected override void OnMessage(Message inMsg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      inMsg.Formatter = GetFormatter();
                                                                                                                                                                                                      对象 inBody = GetTypedMessageBody(inMsg);
                                                                                                                                                                                                      if (inBody!= null)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          ProcessMessage(inBody, inMsg);
                                                                                                                                                                                                      }
                                                                                                                                                                                              
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              public class Re­questReplyS­er­vice : MQSer­vice
                                                                                                                                                                                              {
                                                                                                                                                                                                  public Re­questReplyS­er­vice(IMes­sageRe­ceiver re­ceiver) : base(re­ceiver) {}
                                                                                                                                                                                                  public Re­questReplyS­er­vice(String re­questQueueName) : base (re­questQueueName) {}
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted over­ride Type GetRe­quest­Bo­dy­Type()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      return typeof(System.String);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted vir­tual Object Pro­cess­Mes­sage(Object o)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      String body = (String)o;
                                                                                                                                                                                                      Con­sole.WriteLine("Re­ceived Mes­sage: " + body);
                                                                                                                                                                                                      return body;
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted over­ride void On­Mes­sage(Mes­sage inMsg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      inMsg.Format­ter =  Get­Format­ter();
                                                                                                                                                                                                      Object inBody = Get­TypedMes­sage­Body(inMsg);
                                                                                                                                                                                                      if (inBody != null)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          Object out­Body = Pro­cess­Mes­sage(inBody);
                                                                                                                                                                                              
                                                                                                                                                                                                          if (out­Body != null)
                                                                                                                                                                                                          {
                                                                                                                                                                                                              SendReply(out­Body, inMsg);
                                                                                                                                                                                                          }
                                                                                                                                                                                                      }
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              public class Asyn­cRe­questReplyS­er­vice : MQSer­vice
                                                                                                                                                                                              {
                                                                                                                                                                                                  public Asyn­cRe­questReplyS­er­vice(IMes­sageRe­ceiver re­ceiver) : base(re­ceiver) {}
                                                                                                                                                                                                  public Asyn­cRe­questReplyS­er­vice(String re­questQueueName) : base (re­questQueueName) {}
                                                                                                                                                                                              
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted over­ride Type GetRe­quest­Bo­dy­Type()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      return typeof(System.String);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted vir­tual void Pro­cess­Mes­sage(Object o, Mes­sage msg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      String body = (String)o;
                                                                                                                                                                                                      Con­sole.WriteLine("Re­ceived Mes­sage: " + body);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted over­ride void On­Mes­sage(Mes­sage inMsg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      inMsg.Format­ter =  Get­Format­ter();
                                                                                                                                                                                                      Object inBody = Get­TypedMes­sage­Body(inMsg);
                                                                                                                                                                                                      if (inBody != null)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          Pro­cess­Mes­sage(inBody, inMsg);
                                                                                                                                                                                                      }
                                                                                                                                                                                              
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              这两个类都提供GetRequestBodyType 和ProcessMessage方法的默认实现。GetRequestBodyType指定消息需要一个简单的字符串, ProcessMessage 将该字符串到控制台。从技术上讲,我们可以从RequestReplyService 和AsyncRequestReplyService类中省略这些方法的默认实现以便它们保持抽象。这将允许编译器检测到这些类之一的任何子类忘记实现抽象方法之一。然而,最好有一个可用于测试和调试目的的服务的默认实现,因此我们让这些类具体化,以便它们可以按原样实例化。

                                                                                                                                                                                              Both classes provide a de­fault im­ple­ment­a­tion of the GetRe­quest­Bo­dy­Type and Pro­cess­Mes­sage meth­ods. GetRe­quest­Bo­dy­Type spe­cifies that the mes­sage ex­pects a simple string, and Pro­cess­Mes­sage prints that string to the con­sole. Tech­nic­ally speak­ing, we could have omit­ted the de­fault im­ple­ment­a­tions of these meth­ods from the classes Re­questReplyS­er­vice and Asyn­cRe­questReplyS­er­vice so that they remain ab­stract. This would allow the com­piler to detect any sub­class of one of these classes that forgot to im­ple­ment one of the ab­stract meth­ods. How­ever, it is nice to have a de­fault im­ple­ment­a­tion of a ser­vice avail­able for test­ing and de­bug­ging pur­poses, so we let these classes be con­crete so that they can be in­stan­ti­ated as is.

                                                                                                                                                                                              总之,基类的类图如下所示(我们将很快讨论银行、信用局和贷款经纪人类):

                                                                                                                                                                                              In sum­mary, the class dia­gram for the base classes looks as fol­lows (we dis­cuss the bank, credit bureau, and loan broker classes shortly):

                                                                                                                                                                                              消息服务的基类

                                                                                                                                                                                              Base Classes for Mes­sage Ser­vices

                                                                                                                                                                                              图形/09inf14.gif

                                                                                                                                                                                              设计银行

                                                                                                                                                                                              Design­ing the Bank

                                                                                                                                                                                              现在我们已经创建了一组基类和实用函数,是时候开始实现应用程序逻辑了。开始创建解决方案的一种简单方法是按依赖关系的相反顺序构建应用程序组件。这意味着,我们首先创建不依赖于其他任何东西的组件。这使我们能够独立运行和测试这些组件。银行无疑是其中之一。贷款经纪人依赖于银行,但银行本身是独立的。很方便的是,银行是请求-回复服务的一个主要示例,因此实现银行应该像继承RequestReplyService 并填充一些业务逻辑一样简单。

                                                                                                                                                                                              Now that we have cre­ated a set of base classes and util­ity func­tions, it is time to start im­ple­ment­ing the ap­plic­a­tion logic. An easy way to start cre­at­ing the solu­tion is to build the ap­plic­a­tion com­pon­ents by re­verse order of de­pend­ency. This means, we first create com­pon­ents that do not depend on any­thing else. This allows us to run and test these com­pon­ents in­de­pend­ently. The bank is cer­tainly one of those com­pon­ents. The loan broker de­pends on the banks, but the banks them­selves are self-con­tained. Con­veni­ently enough, a bank is a prime ex­ample of a re­quest-reply-ser­vice, so im­ple­ment­ing a bank should be as simple as in­her­it­ing from Re­questReplyS­er­vice and filling in some busi­ness logic.

                                                                                                                                                                                              不过,在开始研究银行的内部结构之前,我们应该定义外部接口。我们需要定义贷款报价请求和回复的消息类型。为了我们的简单实现,我们为所有银行定义了一个通用消息格式,这样我们就可以为所有五个银行实例使用一个通用类。C# 支持结构体,因此我们使用它们作为消息类型:

                                                                                                                                                                                              Before we start work­ing on the in­tern­als of the bank, though, we should define the ex­ternal in­ter­face. We need to define the mes­sage types for loan quote re­quests and replies. For our simple im­ple­ment­a­tion, we define a common mes­sage format for all banks so we can use a common class for all five bank in­stances. C# sup­ports structs, so we use those as mes­sage types:

                                                                                                                                                                                              银行消息类型
                                                                                                                                                                                              公共结构 BankQuoteRequest
                                                                                                                                                                                              {
                                                                                                                                                                                                  公共 int SSN;
                                                                                                                                                                                                  公共 int 信用评分;
                                                                                                                                                                                                  公共 int 历史长度;
                                                                                                                                                                                                  公共 int 贷款金额;
                                                                                                                                                                                                  公共 int LoanTerm;
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              公共结构 BankQuoteReply
                                                                                                                                                                                              {
                                                                                                                                                                                                  公共双倍利率;
                                                                                                                                                                                                  公共字符串报价ID;
                                                                                                                                                                                                  公共 int 错误代码;
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              public struct BankQuote­Re­quest
                                                                                                                                                                                              {
                                                                                                                                                                                                  public int SSN;
                                                                                                                                                                                                  public int Cred­itScore;
                                                                                                                                                                                                  public int His­toryLength;
                                                                                                                                                                                                  public int LoanAmount;
                                                                                                                                                                                                  public int LoanTerm;
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              public struct BankQuoteReply
                                                                                                                                                                                              {
                                                                                                                                                                                                  public double In­teres­tRate;
                                                                                                                                                                                                  public String QuoteID;
                                                                                                                                                                                                  public int Er­ror­Code;
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              因为我们希望对所有银行实例使用单个类,所以我们需要针对不同的行为对银行进行参数化。我们的银行是非常简单的机构,因此唯一的参数是BankName 、 RatePremium和MaxLoanTerm 。RatePremium决定了银行收取高于优惠利率的利率点数,基本上就是银行的利润率。MaxLoanTerm指定银行愿意延长的最长贷款期限(以月为单位)如果贷款请求的期限比规定的时间长,值得庆幸的是,银行会拒绝。插入适当的便捷构造函数和访问器后,我们可以构建ProcessMessage银行方法:

                                                                                                                                                                                              Be­cause we want to use a single class for all bank in­stances, we need to para­met­er­ize the banks for dif­fer­ent be­ha­vior. Our banks are very simple in­sti­tu­tions, so the only para­met­ers are Bank­Name, Rate­Premium, and MaxLoanTerm. The Rate­Premium de­term­ines the number of in­terest rate points that the bank charges above the prime rate­basic­ally, the bank's profit margin. The MaxLoanTerm spe­cifies the longest loan term (in months) that the bank is will­ing to extend. If a loan re­quest is for a longer dur­a­tion than spe­cified, the bank will thank­fully de­cline. After plug­ging in the ap­pro­pri­ate con­veni­ence con­struct­ors and ac­cessors, we can build the Pro­cess­Mes­sage method of the bank:

                                                                                                                                                                                              银行.cs
                                                                                                                                                                                              内部类 Bank : RequestReplyService
                                                                                                                                                                                              {
                                                                                                                                                                                                ...
                                                                                                                                                                                              
                                                                                                                                                                                                受保护的覆盖类型 GetRequestBodyType()
                                                                                                                                                                                                {
                                                                                                                                                                                                    返回类型(BankQuoteRequest);
                                                                                                                                                                                                }
                                                                                                                                                                                              
                                                                                                                                                                                                受保护的 BankQuoteReply ComputeBankReply(BankQuoteRequest requestStruct)
                                                                                                                                                                                                {
                                                                                                                                                                                                    BankQuoteReplyreplyStruct = new BankQuoteReply();
                                                                                                                                                                                              
                                                                                                                                                                                                    if (requestStruct.LoanTerm <= MaxLoanTerm)
                                                                                                                                                                                                    {
                                                                                                                                                                                                      replyStruct.InterestRate = PrimeRate + RatePremium
                                                                                                                                                                                                                                 + (双)(requestStruct.LoanTerm / 12)/10
                                                                                                                                                                                                                                 + (双)随机.Next(10) / 10;
                                                                                                                                                                                                        回复结构.ErrorCode = 0;
                                                                                                                                                                                                    }
                                                                                                                                                                                                    别的
                                                                                                                                                                                                    {
                                                                                                                                                                                                        回复结构.InterestRate = 0.0;
                                                                                                                                                                                                        回复结构.ErrorCode = 1;
                                                                                                                                                                                                    }
                                                                                                                                                                                                    replyStruct.QuoteID = String.Format("{0}-{1:00000}", BankName, quoteCounter);
                                                                                                                                                                                                    报价计数器++;
                                                                                                                                                                                                    返回回复结构体;
                                                                                                                                                                                                }
                                                                                                                                                                                              
                                                                                                                                                                                                受保护的覆盖对象 ProcessMessage(Object o)
                                                                                                                                                                                                {
                                                                                                                                                                                                    BankQuoteRequest requestStruct;
                                                                                                                                                                                                    BankQuoteReply回复结构;
                                                                                                                                                                                              
                                                                                                                                                                                                    requestStruct = (BankQuoteRequest)o;
                                                                                                                                                                                                    回复结构 = ComputeBankReply(requestStruct);
                                                                                                                                                                                              
                                                                                                                                                                                                    Console.WriteLine("收到了 {1:c}/{2} 个月的 SSN {0} 请求",
                                                                                                                                                                                                                      requestStruct.SSN、requestStruct.LoanAmount、
                                                                                                                                                                                                                      requestStruct.LoanTerm);
                                                                                                                                                                                                    Thread.Sleep(随机.Next(10) * 100);
                                                                                                                                                                                                    Console.WriteLine("报价:{0} {1} {2}",
                                                                                                                                                                                                                      回复结构.ErrorCode, 回复结构.InterestRate,
                                                                                                                                                                                                                      回复结构.QuoteID);
                                                                                                                                                                                              
                                                                                                                                                                                                    返回回复结构体;
                                                                                                                                                                                                }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              in­ternal class Bank : Re­questReplyS­er­vice
                                                                                                                                                                                              {
                                                                                                                                                                                                ...
                                                                                                                                                                                              
                                                                                                                                                                                                pro­tec­ted over­ride Type GetRe­quest­Bo­dy­Type()
                                                                                                                                                                                                {
                                                                                                                                                                                                    return typeof(BankQuote­Re­quest);
                                                                                                                                                                                                }
                                                                                                                                                                                              
                                                                                                                                                                                                pro­tec­ted  BankQuoteReply Com­pute­BankReply(BankQuote­Re­quest re­quest­Struct)
                                                                                                                                                                                                {
                                                                                                                                                                                                    BankQuoteReply reply­S­truct = new BankQuoteReply();
                                                                                                                                                                                              
                                                                                                                                                                                                    if (re­quest­Struct.LoanTerm <= MaxLoanTerm)
                                                                                                                                                                                                    {
                                                                                                                                                                                                      reply­S­truct.In­teres­tRate = PrimeR­ate + Rate­Premium
                                                                                                                                                                                                                                 + (double)(re­quest­Struct.LoanTerm / 12)/10
                                                                                                                                                                                                                                 + (double)random.Next(10) / 10;
                                                                                                                                                                                                        reply­S­truct.Er­ror­Code = 0;
                                                                                                                                                                                                    }
                                                                                                                                                                                                    else
                                                                                                                                                                                                    {
                                                                                                                                                                                                        reply­S­truct.In­teres­tRate = 0.0;
                                                                                                                                                                                                        reply­S­truct.Er­ror­Code = 1;
                                                                                                                                                                                                    }
                                                                                                                                                                                                    reply­S­truct.QuoteID = String.Format("{0}-{1:00000}", Bank­Name, quote­Counter);
                                                                                                                                                                                                    quote­Counter++;
                                                                                                                                                                                                    return reply­S­truct;
                                                                                                                                                                                                }
                                                                                                                                                                                              
                                                                                                                                                                                                pro­tec­ted over­ride Object Pro­cess­Mes­sage(Object o)
                                                                                                                                                                                                {
                                                                                                                                                                                                    BankQuote­Re­quest re­quest­Struct;
                                                                                                                                                                                                    BankQuoteReply reply­S­truct;
                                                                                                                                                                                              
                                                                                                                                                                                                    re­quest­Struct = (BankQuote­Re­quest)o;
                                                                                                                                                                                                    reply­S­truct = Com­pute­BankReply(re­quest­Struct);
                                                                                                                                                                                              
                                                                                                                                                                                                    Con­sole.WriteLine("Re­ceived re­quest for SSN {0} for {1:c} / {2} months",
                                                                                                                                                                                                                      re­quest­Struct.SSN, re­quest­Struct.LoanAmount,
                                                                                                                                                                                                                      re­quest­Struct.LoanTerm);
                                                                                                                                                                                                    Thread.Sleep(random.Next(10) * 100);
                                                                                                                                                                                                    Con­sole.WriteLine("  Quote: {0} {1} {2}",
                                                                                                                                                                                                                      reply­S­truct.Er­ror­Code, reply­S­truct.In­teres­tRate,
                                                                                                                                                                                                                      reply­S­truct.QuoteID);
                                                                                                                                                                                              
                                                                                                                                                                                                    return reply­S­truct;
                                                                                                                                                                                                }
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              我们可以看到,具体服务只需实现GetRequestBodyType 和ProcessMessage方法。该服务可以安全地转换 ProcessMessage 传入的对象,因为基类已经验证了正确的类型。正如我们所看到的,其余的实现与消息传递几乎没有关系,所有细节都在基类中处理。MQService和RequestReplyService 类充当服务激活器,使应用程序不必深入研究消息传递系统的详细信息。

                                                                                                                                                                                              We can see that the con­crete ser­vice has to im­ple­ment only the GetRe­quest­Bo­dy­Type and Pro­cess­Mes­sage meth­ods. The ser­vice can safely cast the object passed in by Pro­cess­Mes­sage be­cause the base class has already veri­fied the cor­rect type. As we can see, the re­main­ing im­ple­ment­a­tion has rather little to do with mes­sagin­gall the de­tails are taken care of in the base classes. The MQSer­vice and Re­questReplyS­er­vice classes act as a Ser­vice Ac­tiv­ator , keep­ing the ap­plic­a­tion from having to dig into mes­saging system de­tails.

                                                                                                                                                                                              ComputeBankReply方法包含银行的完整业务逻辑。生活要是这么简单就好了!嗯,这不是宏观经济学的介绍,而是消息传递的示例,因此我们采取了一些自由来简化事情。计算的利率是最优惠利率、配置的利率溢价、贷款期限和一些随机性的总和。如果请求的贷款期限长于银行可接受的期限,则会返回错误代码。银行发出的每个报价都会收到一个唯一的报价 ID,以便客户稍后可以参考。在当前的实现中,一个简单的递增计数器创建这些 ID。

                                                                                                                                                                                              The method Com­pute­BankReply con­tains the com­plete busi­ness logic for a bank. If life were only so simple! Well, this is not an in­tro­duc­tion to mac­roe­co­nom­ics, but an ex­ample of mes­saging, so we took some liber­ties to sim­plify things. The com­puted in­terest rate is the sum of the prime rate, the con­figured rate premium, the loan term, and a sprinkle of ran­dom­ness. If the re­ques­ted loan term is longer than the bank is com­fort­able with, it re­turns an error code. Each quote that the bank issues re­ceives a unique quote ID so the cus­tomer may refer back to it later. In the cur­rent im­ple­ment­a­tion, a simple in­cre­ment­ing counter cre­ates these IDs.

                                                                                                                                                                                              ProcessMessage方法包含一个小延迟(1/10 到 1 秒之间),使银行交易更加真实。ProcessMessage还将一些活动记录到控制台,以便我们可以看到当我们在简单的控制台应用程序中运行它时发生了什么。

                                                                                                                                                                                              The Pro­cess­Mes­sage method in­cor­por­ates a small delay (between 1/10 and 1 second) to make the bank trans­ac­tion a bit more real­istic. The Pro­cess­Mes­sage also logs some activ­it­ies to the con­sole so we can see what is going on when we run it inside a simple con­sole ap­plic­a­tion.

                                                                                                                                                                                              要启动银行,首先我们使用适当的参数实例化它,然后调用它从 MQService 继承的Run方法。 由于处理是通过事件进行的,因此Run方法会立即返回。因此,我们一定要注意不要在程序启动后就终止它。对于我们的简单测试,我只需在调用Run之后插入Console.ReadLine()语句。

                                                                                                                                                                                              To start a bank, first we in­stan­ti­ate it with the ap­pro­pri­ate para­met­ers, and then we call the Run method that it in­her­its from MQSer­vice. Since the pro­cess­ing hap­pens through events, the Run method re­turns right away. There­fore, we must be care­ful not to ter­min­ate the pro­gram right after it starts. For our simple tests, I simply insert a Con­sole.Read­Line() state­ment after the call to Run.

                                                                                                                                                                                              设计信用局

                                                                                                                                                                                              Design­ing the Credit Bureau

                                                                                                                                                                                              信用局的实施类似于银行。唯一的区别在于消息类型和业务逻辑。信用局可以处理以下消息类型:

                                                                                                                                                                                              The credit bureau im­ple­ment­a­tion is ana­log­ous to the bank. The only dif­fer­ence is in the mes­sage types and the busi­ness logic. The credit bureau can handle the fol­low­ing mes­sage types:

                                                                                                                                                                                              信用局的消息类型
                                                                                                                                                                                              公共课 CreditBureauRequest
                                                                                                                                                                                              {
                                                                                                                                                                                                  公共 int SSN;
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              公开课 CreditBureauReply
                                                                                                                                                                                              {
                                                                                                                                                                                                  公共 int SSN;
                                                                                                                                                                                                  公共 int 信用评分;
                                                                                                                                                                                                  公共 int 历史长度;
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              public class Cred­it­Bur­eauRe­quest
                                                                                                                                                                                              {
                                                                                                                                                                                                  public int SSN;
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              public class Cred­it­Bur­eauReply
                                                                                                                                                                                              {
                                                                                                                                                                                                  public int SSN;
                                                                                                                                                                                                  public int Cred­itScore;
                                                                                                                                                                                                  public int His­toryLength;
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              ProcessMessage方法与银行代码几乎相同,只是它处理不同的数据结构并调用不同的应用程序逻辑。信用局也有一个内置的延迟。

                                                                                                                                                                                              The Pro­cess­Mes­sage method is nearly identical to the bank code, except it deals with dif­fer­ent data struc­tures and in­vokes dif­fer­ent ap­plic­a­tion logic. The credit bureau also has a built-in delay.

                                                                                                                                                                                              CreditBureau.cs
                                                                                                                                                                                              私有 int getCreditScore(int ssn)
                                                                                                                                                                                              {
                                                                                                                                                                                                  return (int)(随机.Next(600) + 300);
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              私有 int getCreditHistoryLength(int ssn)
                                                                                                                                                                                              {
                                                                                                                                                                                                  return (int)(随机.Next(19) + 1);
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              private int getCred­itScore(int ssn)
                                                                                                                                                                                              {
                                                                                                                                                                                                  return (int)(random.Next(600) + 300);
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              private int getCreditH­is­toryLength(int ssn)
                                                                                                                                                                                              {
                                                                                                                                                                                                  return (int)(random.Next(19) + 1);
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              设计贷款经纪人

                                                                                                                                                                                              Design­ing the Loan Broker

                                                                                                                                                                                              现在我们有了一个正常运作的信用局和一个银行类,可以让我们实例化银行的多个化身,我们准备好进行贷款经纪人的内部设计。本书中的路由和转换模式帮助我们细分贷款经纪人需要提供的功能。我们可以将贷款经纪人的内部功能分为三个主要部分(见图):接受客户请求的请求-答复接口、征信局接口和银行接口。

                                                                                                                                                                                              Now that we have a func­tion­ing credit bureau and a bank class that lets us in­stan­ti­ate mul­tiple in­carn­a­tions of a bank, we are ready to work in the in­ternal design of the loan broker. The rout­ing and trans­form­a­tion pat­terns from this book help us seg­ment the func­tions that the loan broker needs to provide. We can group the in­ternal func­tions of the loan broker into three main por­tions (see figure): the re­quest-reply in­ter­face that ac­cepts re­quests from cli­ents, the credit bureau in­ter­face, and the bank in­ter­face.

                                                                                                                                                                                              贷款经纪人的内部结构

                                                                                                                                                                                              In­ternal Struc­ture of the Loan Broker

                                                                                                                                                                                              图形/09inf15.gif

                                                                                                                                                                                              与以相反的依赖顺序构建整个解决方案类似,让我们开始构建仅依赖于已有内容的部分。因为我们刚刚构建了一家银行和一个信用局服务,所以创建从贷款经纪人到这些外部组件的接口是有意义的。信用局界面看起来确实更简单,所以让我们从这里开始。

                                                                                                                                                                                              In a sim­ilar fash­ion to build­ing the whole solu­tion in re­verse order of de­pend­ency, let's start build­ing the pieces that depend only on what's already there. Be­cause we just built a bank and a credit bureau ser­vice, it makes sense to create the in­ter­face from the loan broker to these ex­ternal com­pon­ents. The credit bureau in­ter­face def­in­itely seems sim­pler, so let's start there.

                                                                                                                                                                                              信用局网关

                                                                                                                                                                                              贷款经纪人需要向信用局提出请求,以获得银行要求的客户信用评级。这意味着向外部贷款经纪人组件发送消息并接收回复消息。将发送通用消息的详细信息封装在 MessageGateway 中,使我们能够向应用程序的其余部分隐藏许多 MSMQ 详细信息。遵循相同的推理,我们应该将向信用局发送和接收消息封装在信用局网关内。该信用局网关执行语义丰富的重要功能,允许贷款经纪人调用 GetCreditScore 等方法,而不是调用之类的方法。发送消息。这使得贷款经纪人代码更具可读性,并为贷款经纪人和征信局之间的通信提供了强有力的封装。下图说明了通过“链接”两个网关实现的抽象级别。

                                                                                                                                                                                              The loan broker needs to make re­quests to the credit bureau to obtain the cus­tomer's credit rating, which is re­quired by the bank. This im­plies send­ing a mes­sage to the ex­ternal loan broker com­pon­ent and re­ceiv­ing reply mes­sages. Wrap­ping the de­tails of send­ing a gen­eric mes­sage inside a Mes­sageG­ate­way al­lowed us to hide many MSMQ de­tails from the rest of the ap­plic­a­tion. Fol­low­ing the same reas­on­ing, we should en­cap­su­late send­ing and re­ceiv­ing mes­sages to the credit bureau inside a credit bureau gate­way. This credit bureau gate­way per­forms the im­port­ant func­tion of se­mantic en­rich­ment, al­low­ing the loan broker to call meth­ods such as GetCred­itScore as op­posed to SendMes­sage. This makes the loan broker code more read­able and provides a strong en­cap­su­la­tion of the com­mu­nic­a­tion between the loan broker and the credit bureau. The fol­low­ing dia­gram il­lus­trates the levels of ab­strac­tion achieved by "chain­ing" the two gate­ways.

                                                                                                                                                                                              贷款经纪人为消息传递基础设施提供了额外的抽象级别

                                                                                                                                                                                              The Loan Broker Provides an Ad­di­tional Level of Ab­strac­tion from the Mes­saging In­fra­struc­ture

                                                                                                                                                                                              图形/09inf16.gif

                                                                                                                                                                                              为了请求信用评分,网关需要创建CreditBureauRequest 结构的实例,如信用局指定的。同样,该接口将在CreditBureauReply内接收结果结构。我们之前说过,该解决方案是由单独的可执行文件构建的,以便信用局可以在与贷款经纪人不同的计算机上运行。但这意味着贷款经纪人可能无法访问信用局程序集中定义的类型,而且实际上,我们不希望贷款经纪人对信用局内部进行任何引用,因为这会消除松散的好处通过消息队列进行耦合。贷款经纪人应该完全不知道信用评分请求哪些组件服务。然而,贷款经纪人需要访问定义消息格式的结构。幸运的是,Microsoft .NET Framework SDK 包含一个工具可以让我们做到这一点,即 XML 架构定义工具 ( xsd.exe)。该工具可以从程序集创建 XML 架构,还可以从 XML 架构创建 C# 源代码。下图描述了该过程:

                                                                                                                                                                                              In order to re­quest a credit score, the gate­way needs to create an in­stance of a Cred­it­Bur­eauRe­quest struct, as spe­cified by credit bureau. Like­wise, the in­ter­face will re­ceive the res­ults inside a Cred­it­Bur­eauReply struct. We stated earlier that the solu­tion is built from sep­ar­ate ex­ecut­ables so that the credit bureau can run on a dif­fer­ent com­puter than the loan broker. This means, though, that the loan broker may not have access to types defined in the credit bureau's as­sembly, and really, we would not want the loan broker to make any ref­er­ences to the credit bureau in­tern­als, be­cause that would elim­in­ate the be­ne­fits of loose coup­ling over mes­sage queues. The loan broker is sup­posed to be com­pletely un­aware of what com­pon­ent ser­vices the credit score re­quests. The loan broker needs, how­ever, access to the structs that define the mes­sage formats. Luck­ily, the Mi­crosoft .NET Frame­work SDK con­tains a tool that lets us do just that, the XML Schema Defin­i­tion Tool (xsd.exe). This tool can create XML schemas from an as­sembly and also create C# source code from XML schemas. The fol­low­ing figure de­scribes the pro­cess:

                                                                                                                                                                                              从另一个程序集创建类存根

                                                                                                                                                                                              Cre­at­ing Class Stubs from An­other As­sembly

                                                                                                                                                                                              图形/09inf17.gif

                                                                                                                                                                                              xsd.exe提取公共类型定义并根据类型定义和控制序列化的可选属性创建 XML 架构文件。在我们的例子中,xsd.exe创建了以下架构:

                                                                                                                                                                                              xsd.exe ex­tracts public type defin­i­tions and cre­ates an XML schema file based on the type defin­i­tion and op­tional at­trib­utes that con­trol seri­al­iz­a­tion. In our case, xsd.exe cre­ated the fol­low­ing schema:

                                                                                                                                                                                              <?xml 版本=“1.0”编码=“utf-8”?>
                                                                                                                                                                                              <xs:schema elementFormDefault="合格" xmlns:xs="http://www.w3.org/2001/XMLSchema">
                                                                                                                                                                                                <xs:element name="CreditBureauRequest" type="CreditBureauRequest" />
                                                                                                                                                                                                <xs:complexType name="CreditBureauRequest">
                                                                                                                                                                                                  <xs:序列>
                                                                                                                                                                                                    <xs:element minOccurs="1" maxOccurs="1" name="SSN" type="xs:int" />
                                                                                                                                                                                                  </xs:序列>
                                                                                                                                                                                                </xs:复杂类型>
                                                                                                                                                                                                <xs:element name="CreditBureauReply" type="CreditBureauReply" />
                                                                                                                                                                                                <xs:complexType name="CreditBureauReply">
                                                                                                                                                                                                  <xs:序列>
                                                                                                                                                                                                    <xs:element minOccurs="1" maxOccurs="1" name="SSN" type="xs:int" />
                                                                                                                                                                                                    <xs:element minOccurs="1" maxOccurs="1" name="CreditScore" type="xs:int" />
                                                                                                                                                                                                    <xs:element minOccurs="1" maxOccurs="1" name="HistoryLength" type="xs:int" />
                                                                                                                                                                                                  </xs:序列>
                                                                                                                                                                                                </xs:复杂类型>
                                                                                                                                                                                                <xs:element name="Run" nillable="true" type="Run" />
                                                                                                                                                                                                <xs:complexType name="运行"/>
                                                                                                                                                                                              </xs:架构>
                                                                                                                                                                                              
                                                                                                                                                                                              <?xml ver­sion="1.0" en­cod­ing="utf-8"?>
                                                                                                                                                                                              <xs:schema ele­ment­Form­De­fault="qual­i­fied" xmlns:xs="http://www.w3.org/2001/XMLS­chema">
                                                                                                                                                                                                <xs:ele­ment name="Cred­it­Bur­eauRe­quest" type="Cred­it­Bur­eauRe­quest" />
                                                                                                                                                                                                <xs:com­plex­Type name="Cred­it­Bur­eauRe­quest">
                                                                                                                                                                                                  <xs:se­quence>
                                                                                                                                                                                                    <xs:ele­ment minOc­curs="1" maxOc­curs="1" name="SSN" type="xs:int" />
                                                                                                                                                                                                  </xs:se­quence>
                                                                                                                                                                                                </xs:com­plex­Type>
                                                                                                                                                                                                <xs:ele­ment name="Cred­it­Bur­eauReply" type="Cred­it­Bur­eauReply" />
                                                                                                                                                                                                <xs:com­plex­Type name="Cred­it­Bur­eauReply">
                                                                                                                                                                                                  <xs:se­quence>
                                                                                                                                                                                                    <xs:ele­ment minOc­curs="1" maxOc­curs="1" name="SSN" type="xs:int" />
                                                                                                                                                                                                    <xs:ele­ment minOc­curs="1" maxOc­curs="1" name="Cred­itScore" type="xs:int" />
                                                                                                                                                                                                    <xs:ele­ment minOc­curs="1" maxOc­curs="1" name="His­toryLength" type="xs:int" />
                                                                                                                                                                                                  </xs:se­quence>
                                                                                                                                                                                                </xs:com­plex­Type>
                                                                                                                                                                                                <xs:ele­ment name="Run" nil­lable="true" type="Run" />
                                                                                                                                                                                                <xs:com­plex­Type name="Run" />
                                                                                                                                                                                              </xs:schema>
                                                                                                                                                                                              

                                                                                                                                                                                              通常,服务会将此架构定义发布给潜在的调用者。这使得调用者可以选择以多种不同的方式生成所需的消息格式。首先,调用者可以显式构造符合 XSD 的请求消息。或者,调用者可以使用 .NET 内置序列化。在这种情况下,客户端仍然可以选择不同的编程语言,因为 .NET CLR 是独立于编程语言的。

                                                                                                                                                                                              Usu­ally, a ser­vice would pub­lish this schema defin­i­tion to po­ten­tial callers. This allows the caller the option to pro­duce the re­quired mes­sage format in a number of dif­fer­ent ways. First, the caller could con­struct the XSD-com­pli­ant re­quest mes­sage ex­pli­citly. Al­tern­at­ively, the caller could use the .NET built-in seri­al­iz­a­tion. In this case, the client would still have a choice of dif­fer­ent pro­gram­ming lan­guages since the .NET CLR is pro­gram­ming lan­guageinde­pend­ent.

                                                                                                                                                                                              我们决定使用.NET 的内置序列化。因此,我们再次运行xsd.exe来创建供服务使用者使用的源文件,我们得到一个如下所示的文件:

                                                                                                                                                                                              We decide to use .NET's built-in seri­al­iz­a­tion. There­fore, we run xsd.exe again to create source files to be used by the ser­vice con­sumer, and we get a file that looks like this:

                                                                                                                                                                                              //
                                                                                                                                                                                              // 该源代码由 xsd 自动生成,版本=1.1.4322.573。
                                                                                                                                                                                              //
                                                                                                                                                                                              命名空间 CreditBureau {
                                                                                                                                                                                                  使用 System.Xml.Serialization;
                                                                                                                                                                                              
                                                                                                                                                                                              
                                                                                                                                                                                                  /// <备注/>
                                                                                                                                                                                                  [System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)]
                                                                                                                                                                                                  公共类 CreditBureauRequest {
                                                                                                                                                                                              
                                                                                                                                                                                                      /// <备注/>
                                                                                                                                                                                                      公共 int SSN;
                                                                                                                                                                                                  }
                                                                                                                                                                                                  ...
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              //
                                                                                                                                                                                              // This source code was auto-gen­er­ated by xsd, Ver­sion=1.1.4322.573.
                                                                                                                                                                                              //
                                                                                                                                                                                              namespace Cred­it­Bur­eau {
                                                                                                                                                                                                  using System.Xml.Seri­al­iz­a­tion;
                                                                                                                                                                                              
                                                                                                                                                                                              
                                                                                                                                                                                                  /// <re­marks/>
                                                                                                                                                                                                  [System.Xml.Seri­al­iz­a­tion.Xm­l­RootAt­trib­ute(Namespace="", Is­Nul­lable=false)]
                                                                                                                                                                                                  public class Cred­it­Bur­eauRe­quest {
                                                                                                                                                                                              
                                                                                                                                                                                                      /// <re­marks/>
                                                                                                                                                                                                      public int SSN;
                                                                                                                                                                                                  }
                                                                                                                                                                                                  ...
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              值得注意的是.NET XML 序列化和反序列化允许松散耦合。因此,从技术上讲,我们发送给征信局的请求消息不必与征信局实现内部使用的 CLR 类型完全相同,只要 XML 表示形式包含所需的元素即可。例如,这将允许请求者发送 XML 表示包含附加元素的消息,而不会干扰通信。对于我们的示例,我们假设贷款经纪人愿意遵守信用局指定的格式并在通信两端使用相同的数据类型。

                                                                                                                                                                                              It is worth noting that the .NET XML seri­al­iz­a­tion and deseri­al­iz­a­tion allows loose coup­ling. So, tech­nic­ally speak­ing, the re­quest mes­sage that we send to the credit bureau does not have to be of the exact same CLR type that is used inside the credit bureau im­ple­ment­a­tion as long as the XML rep­res­ent­a­tion con­tains the re­quired ele­ments. For ex­ample, this would allow a re­questor to send a mes­sage whose XML rep­res­ent­a­tion con­tains ad­di­tional ele­ments without dis­turb­ing the com­mu­nic­a­tion. For our ex­ample, we assume that the loan broker is will­ing to con­form to the credit bureau's spe­cified format and use the same data types on both ends of the com­mu­nic­a­tion.

                                                                                                                                                                                              我们现在准备好以正确的格式向信用局发送消息。但我们需要记住,这种通信是异步的,具有单独的异步请求消息和回复消息。我们可以设计信用局网关,以便在发送请求后,网关代码等待直到响应返回。这种方法有一个显着的缺点:当信用局处理消息时,应用程序只会等待。这种类型的伪同步处理很快就会导致性能瓶颈。如果我们使流程的每一步都伪同步,则意味着贷款经纪人一次只能处理一个请求流程。例如,当它仍在等待银行对先前报价请求的答复时,它将无法请求新请求的信用评分。为了直观地看出差异,我们假设贷款经纪人必须执行两个主要步骤:获取信用评分并从银行获得最佳报价。如果我们假设贷款经纪人仅运行单个顺序执行,则执行将如下图上半部分所示:

                                                                                                                                                                                              We are now ready to send mes­sages in the cor­rect format to the credit bureau. We need to keep in mind, though, that this com­mu­nic­a­tion is asyn­chron­ous with a sep­ar­ate, asyn­chron­ous re­quest mes­sage and reply mes­sage. We could design the credit bureau gate­way so that after send­ing a re­quest, the gate­way code waits until the re­sponse comes back. This ap­proach has one sig­ni­fic­ant draw­back: The ap­plic­a­tion will just sit and wait while the credit bureau is pro­cess­ing a mes­sage. This type of pseudo-syn­chron­ous pro­cess­ing can quickly result in a per­form­ance bot­tle­neck. If we make each step of the pro­cess pseudo-syn­chron­ous, it means that the loan broker can pro­cess only one re­quest pro­cess at a time. For ex­ample, it would not be able to re­quest the credit score for a new re­quest while it is still wait­ing for bank replies for the pre­vi­ous quote re­quest. To visu­al­ize the dif­fer­ence, let's con­sider that the loan broker has to per­form two main steps: Get the credit score and get the best quote from the banks. If we assume the loan broker runs only a single, se­quen­tial ex­e­cu­tion, the ex­e­cu­tion will look like the top half of the fol­low­ing figure:

                                                                                                                                                                                              管道处理可以提供显着更高的吞吐量

                                                                                                                                                                                              Pipeline Pro­cess­ing Can Provide Sig­ni­fic­antly Higher Through­put

                                                                                                                                                                                              图形/09inf18.gif

                                                                                                                                                                                              由于大部分实际工作是由外部组件执行的,因此贷款经纪人组件基本上只是等待结果,而不是有效利用计算资源。如果我们将整个贷款经纪人流程设计为事件驱动的消费者,我们就可以开始并行处理多个请求,并在结果传入时对其进行处理。我们将此模式称为管道加工。系统的可扩展性现在仅取决于外部组件的处理能力,而不取决于贷款经纪人。如果我们仅运行信用局进程的单个实例,则差异可能不会那么明显,因为信用局请求队列无论如何都会对请求进行排队。但是,如果我们决定并行运行多个信用局实例,我们将看到立即的性能提升(更多关于性能的信息)。

                                                                                                                                                                                              Be­cause the bulk of the actual work is ex­ecuted by ex­ternal com­pon­ents, the loan broker com­pon­ent ba­sic­ally sits around and waits for res­ult­s­not an ef­fi­cient use of com­put­ing re­sources. If we design the whole loan broker pro­cess as an Event-Driven Con­sumer, we can start pro­cess­ing mul­tiple re­quests in par­al­lel and pro­cess the res­ults as they come in. We call this mode pipeline pro­cess­ing. The scalab­il­ity of the system now de­pends only on the pro­cess­ing ca­pa­city of the ex­ternal com­pon­ents and not on the loan broker. If we run only a single in­stance of the credit bureau pro­cess, the dif­fer­ence may not be as pro­nounced be­cause the bureau re­quest queue will queue up the re­quests anyway. How­ever, if we decide to run mul­tiple credit bureau in­stances in par­al­lel, we will see im­me­di­ate per­form­ance gains (more on per­form­ance to follow).

                                                                                                                                                                                              有两种主要方法可以使贷款经纪人流程成为事件驱动的。我们可以创建一个顺序进程,但为每个传入的请求消息创建一个新线程。或者,我们可以让消息系统在事件待处理时通知贷款经纪人。这样,我们就让消息系统控制执行线程。每种方法都有优点和缺点。编写顺序过程可以使代码更容易理解;但是,如果我们的组件主要是在外部实体之间代理消息的代理组件,则可能会导致大量线程除了等待传入消息外什么都不做。这些线程可能会消耗大量系统资源,但完成的任务却很少。所以,让消息系统驱动执行可能会更好。每当消息准备好被使用时,系统就会调用代理执行。这让我们可以维护单个执行线程,而不必担心线程管理。然而,我们需要处理这样一个事实:执行路径不是单个顺序方法,而是随着消息到达而执行的多个代码段。

                                                                                                                                                                                              There are two primary ways to make the loan broker pro­cess event-driven. We can create a se­quen­tial pro­cess but create a new thread for each in­com­ing re­quest mes­sage. Al­tern­at­ively, we can let the mes­saging system notify the loan broker whenever an event is pending. This way, we let the mes­saging system con­trol the threads of ex­e­cu­tion. Each ap­proach has pros and cons. Coding a se­quen­tial pro­cess can make the code easier to un­der­stand; how­ever, if our com­pon­ent is primar­ily a broker com­pon­ent that brokers mes­sages between ex­ternal en­tit­ies, it would result in a po­ten­tially large number of threads that are doing noth­ing much but wait­ing for in­com­ing mes­sages. These threads could con­sume a large number of system re­sources and ac­com­plish little. There­fore, we may be better off let­ting the mes­saging system drive the ex­e­cu­tion. Whenever a mes­sage is ready to be con­sumed, the system will invoke the broker ex­e­cu­tion. This lets us main­tain a single thread of ex­e­cu­tion and not worry about thread man­age­ment. How­ever, we need to deal with the fact that the ex­e­cu­tion path is not a single se­quen­tial method, but mul­tiple code seg­ments that are ex­ecuted as mes­sages arrive.

                                                                                                                                                                                              您可能已经猜到,在 .NET 中使事情成为事件驱动的方法是使用委托。正如预期的那样,信用局网关定义了一个新的委托。

                                                                                                                                                                                              You might have guessed that the way to make things event-driven in .NET is to use del­eg­ates. As ex­pec­ted, the credit bureau gate­way defines a new del­eg­ate.

                                                                                                                                                                                              public delegate void OnCreditReplyEvent(CreditBureauReply CreditReply, Object ACT);
                                                                                                                                                                                              
                                                                                                                                                                                              public del­eg­ate void On­CreditReplyEvent(Cred­it­Bur­eauReply creditReply, Object ACT);
                                                                                                                                                                                              

                                                                                                                                                                                              该委托允许其他代码段告诉信用局网关在结果传入时调用哪个方法。信用局网关将正确类型的CreditBureauReply 结构传递回调用者。它还传递我们称为 ACTan异步完成令牌[ POSA2 ] 的东西。此令牌允许调用者将数据传递到网关,并在相应的回复消息传入时接收返回的数据(请参阅消息传递网关)。基本上,信用局网关对请求和回复消息执行关联,这样调用者就不必这样做。

                                                                                                                                                                                              This del­eg­ate allows other code seg­ments to tell the credit bureau gate­way which method to call when the result comes in. The credit bureau gate­way passes a prop­erly typed Cred­it­Bur­eauReply struct back to the caller. It also passes some­thing we call ACTan Asyn­chron­ous Com­ple­tion Token [POSA2]. This token allows the caller to pass in data to the gate­way and re­ceive the data back when the cor­res­pond­ing reply mes­sage comes in (see Mes­saging Gate­way). Ba­sic­ally, the credit bureau gate­way per­forms cor­rel­a­tion for the re­quest and reply mes­sages so that the caller does not have to.

                                                                                                                                                                                              剩下的是请求信用评分的方法和处理传入回复消息的方法,关联正确的 ACT 并调用正确类型的委托。

                                                                                                                                                                                              What's left are a method to re­quest a credit score and a method that handles an in­com­ing reply mes­sage, cor­rel­at­ing the proper ACT and in­vok­ing the prop­erly typed del­eg­ate.

                                                                                                                                                                                              CreditBureauGateway.cs
                                                                                                                                                                                              内部结构 CreditRequestProcess
                                                                                                                                                                                              {
                                                                                                                                                                                                  公共 int CorrelationID;
                                                                                                                                                                                                  公共对象 ACT;
                                                                                                                                                                                                  公共 OnCreditReplyEvent 回调;
                                                                                                                                                                                              }
                                                                                                                                                                                               内部类 CreditBureauGateway
                                                                                                                                                                                              {
                                                                                                                                                                                                  受保护的 IMessageSender 信用请求队列;
                                                                                                                                                                                                  受保护的 IMessageReceiver 信用回复队列;
                                                                                                                                                                                              
                                                                                                                                                                                                  受保护的 IDictionary activeProcesses = (IDictionary)(new Hashtable());
                                                                                                                                                                                              
                                                                                                                                                                                                  受保护的随机随机 = new Random();
                                                                                                                                                                                              
                                                                                                                                                                                                  公共无效监听()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      CreditReplyQueue.Begin();
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  公共无效GetCreditScore(CreditBureauRequest quoteRequest,
                                                                                                                                                                                                                             OnCreditReplyEvent OnCreditResponse,对象 ACT)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      消息 requestMessage = new Message(quoteRequest);
                                                                                                                                                                                                      requestMessage.ResponseQueue = CreditReplyQueue.GetQueue();
                                                                                                                                                                                                      requestMessage.AppSpecific = random.Next();
                                                                                                                                                                                              
                                                                                                                                                                                                      CreditRequestProcess processInstance = new CreditRequestProcess();
                                                                                                                                                                                                      processInstance.ACT = ACT;
                                                                                                                                                                                                      processInstance.callback = OnCreditResponse;
                                                                                                                                                                                                      processInstance.CorrelationID = requestMessage.AppSpecific;
                                                                                                                                                                                              
                                                                                                                                                                                                      CreditRequestQueue.Send(requestMessage);
                                                                                                                                                                                              
                                                                                                                                                                                                      activeProcesses.Add(processInstance.CorrelationID, processInstance);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  私人无效OnCreditResponse(消息msg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      msg.Formatter = GetFormatter();
                                                                                                                                                                                              
                                                                                                                                                                                                      CreditBureau回复replyStruct;
                                                                                                                                                                                                      尝试
                                                                                                                                                                                                      {
                                                                                                                                                                                                          if (msg.Body 是 CreditBureauReply)
                                                                                                                                                                                                          {
                                                                                                                                                                                                              replyStruct = (CreditBureauReply)msg.Body;
                                                                                                                                                                                                              int CorrelationID = msg.AppSpecific;
                                                                                                                                                                                              
                                                                                                                                                                                                              if (activeProcesses.Contains(CorrelationID))
                                                                                                                                                                                                              {
                                                                                                                                                                                                                  CreditRequestProcess processInstance =
                                                                                                                                                                                                                    (CreditRequestProcess)(activeProcesses[CorrelationID]);
                                                                                                                                                                                                                  processInstance.callback(replyStruct, processInstance.ACT);
                                                                                                                                                                                                                  activeProcesses.Remove(CorrelationID);
                                                                                                                                                                                                              }
                                                                                                                                                                                                              else { Console.WriteLine
                                                                                                                                                                                                                      (“传入的信用响应与任何请求都不匹配”);}
                                                                                                                                                                                                          }
                                                                                                                                                                                                          别的
                                                                                                                                                                                                          { Console.WriteLine("非法回复。"); }
                                                                                                                                                                                                      }
                                                                                                                                                                                                      捕获(异常 e)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          Console.WriteLine("异常:{0}", e.ToString());
                                                                                                                                                                                                      }
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              in­ternal struct CreditRe­quest­Pro­cess
                                                                                                                                                                                              {
                                                                                                                                                                                                  public int Cor­rel­a­tionID;
                                                                                                                                                                                                  public Object ACT;
                                                                                                                                                                                                  public On­CreditReplyEvent call­back;
                                                                                                                                                                                              }
                                                                                                                                                                                               in­ternal class Cred­it­Bur­eau­G­ate­way
                                                                                                                                                                                              {
                                                                                                                                                                                                  pro­tec­ted IMes­sageSender creditRe­questQueue;
                                                                                                                                                                                                  pro­tec­ted IMes­sageRe­ceiver creditReplyQueue;
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted IDic­tion­ary act­ive­Pro­cesses = (IDic­tion­ary)(new Hasht­able());
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted Random random = new Random();
                                                                                                                                                                                              
                                                                                                                                                                                                  public void Listen()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      creditReplyQueue.Begin();
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  public void GetCred­itScore(Cred­it­Bur­eauRe­quest quote­Re­quest,
                                                                                                                                                                                                                             On­CreditReplyEvent On­CreditRe­sponse, Object ACT)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      Mes­sage re­quest­Mes­sage = new Mes­sage(quote­Re­quest);
                                                                                                                                                                                                      re­quest­Mes­sage.Re­spon­se­Queue = creditReplyQueue.GetQueue();
                                                                                                                                                                                                      re­quest­Mes­sage.AppSpe­cific = random.Next();
                                                                                                                                                                                              
                                                                                                                                                                                                      CreditRe­quest­Pro­cess pro­cessIn­stance = new CreditRe­quest­Pro­cess();
                                                                                                                                                                                                      pro­cessIn­stance.ACT = ACT;
                                                                                                                                                                                                      pro­cessIn­stance.call­back = On­CreditRe­sponse;
                                                                                                                                                                                                      pro­cessIn­stance.Cor­rel­a­tionID = re­quest­Mes­sage.AppSpe­cific;
                                                                                                                                                                                              
                                                                                                                                                                                                      creditRe­questQueue.Send(re­quest­Mes­sage);
                                                                                                                                                                                              
                                                                                                                                                                                                      act­ive­Pro­cesses.Add(pro­cessIn­stance.Cor­rel­a­tionID, pro­cessIn­stance);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  private void On­CreditRe­sponse(Mes­sage msg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      msg.Format­ter =  Get­Format­ter();
                                                                                                                                                                                              
                                                                                                                                                                                                      Cred­it­Bur­eauReply reply­S­truct;
                                                                                                                                                                                                      try
                                                                                                                                                                                                      {
                                                                                                                                                                                                          if (msg.Body is Cred­it­Bur­eauReply)
                                                                                                                                                                                                          {
                                                                                                                                                                                                              reply­S­truct = (Cred­it­Bur­eauReply)msg.Body;
                                                                                                                                                                                                              int Cor­rel­a­tionID = msg.AppSpe­cific;
                                                                                                                                                                                              
                                                                                                                                                                                                              if (act­ive­Pro­cesses.Con­tains(Cor­rel­a­tionID))
                                                                                                                                                                                                              {
                                                                                                                                                                                                                  CreditRe­quest­Pro­cess pro­cessIn­stance =
                                                                                                                                                                                                                    (CreditRe­quest­Pro­cess)(act­ive­Pro­cesses[Cor­rel­a­tionID]);
                                                                                                                                                                                                                  pro­cessIn­stance.call­back(reply­S­truct, pro­cessIn­stance.ACT);
                                                                                                                                                                                                                  act­ive­Pro­cesses.Remove(Cor­rel­a­tionID);
                                                                                                                                                                                                              }
                                                                                                                                                                                                              else { Con­sole.WriteLine
                                                                                                                                                                                                                      ("In­com­ing credit re­sponse does not match any re­quest"); }
                                                                                                                                                                                                          }
                                                                                                                                                                                                          else
                                                                                                                                                                                                          { Con­sole.WriteLine("Il­legal reply."); }
                                                                                                                                                                                                      }
                                                                                                                                                                                                      catch (Ex­cep­tion e)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          Con­sole.WriteLine("Ex­cep­tion: {0}", e.To­String());
                                                                                                                                                                                                      }
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              当调用者通过 GetCreditScore 方法请求信用评分时,信用局网关会分配CreditRequestProcess 结构的新实例。activeProcesses集合包含每个未完成实例,由消息的相关标识符作为键控该结构还保存OnCreditReplyEvent事件的委托。存储每条消息的委托允许每个调用者为每个请求指定不同的回调位置。正如我们稍后将看到的,这允许调用者使用委托来管理对话状态。

                                                                                                                                                                                              When a caller re­quests a credit score via the GetCred­itScore­method, the credit bureau gate­way al­loc­ates a new in­stance of the CreditRe­quest­Pro­cess struc­ture. The col­lec­tion act­ive­Pro­cesses con­tains one in­stance of CreditRe­quest­Pro­cess for each out­stand­ing re­quest, keyed by the Cor­rel­a­tion Iden­ti­fier of the mes­sage. The struc­ture also holds the del­eg­ate for the On­CreditReplyEvent event. Stor­ing the del­eg­ate for each mes­sage allows each caller to spe­cify a dif­fer­ent call­back loc­a­tion for each re­quest. As we will see later, this allows the caller to use del­eg­ates to manage con­ver­sa­tion state.

                                                                                                                                                                                              需要注意的是,我们不使用消息的内置消息 ID 字段进行关联。相反,我们为AppSpecific字段分配一个随机整数,并通过该字段的值关联传入消息(请记住,我们设计RequestReplyService 来复制ID 字段和AppSpecific回复消息字段)。为什么我们想要通过消息 ID 以外的其他内容进行关联?消息ID的优点是对于系统中的每条消息来说它是唯一的。但这也限制了我们的灵活性。要求回复消息与请求消息的消息 ID 相关联不允许我们在消息流中插入中间步骤(例如路由器)。由于任何中间步骤都会使用请求消息并向服务发布新消息,因此回复消息的CorrelationId 将与服务收到的消息匹配,但与贷款经纪人最初发送的消息不匹配(见图)。

                                                                                                                                                                                              It is im­port­ant to note that we do not use the mes­sage's built-in mes­sage ID field to cor­rel­ate. In­stead, we assign a random in­teger number to the AppSpe­cific field and cor­rel­ate in­com­ing mes­sages by the value of that field (re­mem­ber that we de­signed the Re­questReplyS­er­vice to copy both the ID field and the AppSpe­cific field to the reply mes­sage). Why would we want to cor­rel­ate by some­thing other than the mes­sage ID? The ad­vant­age of the mes­sage ID is that it is unique for each mes­sage in the system. But that also limits our flex­ib­il­ity. Re­quir­ing a reply mes­sage to cor­rel­ate to the mes­sage ID of the re­quest mes­sage does not allow us to insert in­ter­me­di­ate steps (for ex­ample, a router) into the mes­sage flow. Be­cause any in­ter­me­di­ate step would con­sume the re­quest mes­sage and pub­lish a new mes­sage to the ser­vice, the reply mes­sage's Cor­rel­a­tionId would match the mes­sage the ser­vice re­ceived but not the mes­sage that the loan broker ori­gin­ally sent (see figure).

                                                                                                                                                                                              中介阻碍了与系统生成的消息 ID 的关联

                                                                                                                                                                                              In­ter­me­di­ar­ies Hinder Cor­rel­a­tion with System-Gen­er­ated Mes­sage IDs

                                                                                                                                                                                              图形/09inf19.gif

                                                                                                                                                                                              这个问题有两种解决方案。首先,任何中间体都需要拦截请求和回复消息,并为回复消息配备正确的CorrelationId 值(有关此方法的示例,请参阅智能代理 [xxx] ) 。或者,我们可以使用单独的字段来实现关联目的,以便流经中介和服务的所有相关消息都携带相同的关联标识符在这个例子中,我们选择了第二种方法,使中介组件更容易拦截贷款经纪人和信用局之间的请求消息(我们将在第 12 章中利用这一点),“插曲:系统管理示例。”)我们应该如何为AppSpecific属性选择一个值?我们可以使用顺序值,但是我们必须小心两个并发实例不要使用相同的起始值。我们还可以使用中央 ID 生成模块(例如数据库)来保证系统范围内的唯一性。对于这个简单的例子来说,这似乎有点麻烦,所以我们选择了一个随机数。.NET 将随机数生成为带符号的 32 位整数,因此重复的几率是我们愿意承担的二亿分之一的风险。

                                                                                                                                                                                              There are two solu­tions to this prob­lem. First, any in­ter­me­di­ate would be re­quired to in­ter­cept both re­quest and reply mes­sages and to equip reply mes­sages with the cor­rect Cor­rel­a­tionId value (for an ex­ample of this ap­proach, see the Smart Proxy [xxx]). Al­tern­at­ively, we can use a sep­ar­ate field for cor­rel­a­tion pur­poses so that all re­lated mes­sages that flow through the in­ter­me­di­ary and the ser­vice carry the same Cor­rel­a­tion Iden­ti­fier. In this ex­ample, we chose the second ap­proach to make it easier for an in­ter­me­di­ary com­pon­ent to in­ter­cept re­quest mes­sages between the loan broker and the credit bureau (we will take ad­vant­age of this in Chapter 12, "In­ter­lude: Sys­tems Man­age­ment Ex­ample.") How should we pick a value for the AppSpe­cific prop­erty? We could use se­quen­tial values, but then we have to be care­ful that two con­cur­rent in­stances do not use the same start­ing value. We could also use a cent­ral ID gen­er­a­tion module (e.g., a data­base) that guar­an­tees sys­tem­wide unique­ness. This seemed a little too much trouble for this simple ex­ample, so we chose a random number. .NET gen­er­ates random num­bers as signed 32-bit in­tegers so that the odds of a du­plic­ate are 1 in 2 bil­liona risk we are will­ing to take.

                                                                                                                                                                                              信用局网关现在提供了我们所期望的 Windows 消息队列基础结构的清晰抽象。信用局网关的唯一公共接口(除了构造函数之外)是委托和两个方法:

                                                                                                                                                                                              The credit bureau gate­way now provides the clean ab­strac­tion from the Win­dows mes­sage queuing in­fra­struc­ture that we were aiming for. The only public in­ter­face into the credit bureau gate­way (be­sides con­struct­ors) is a del­eg­ate and two meth­ods:

                                                                                                                                                                                              delegate void OnCreditReplyEvent(CreditBureauReply CreditReply, Object ACT);
                                                                                                                                                                                              CreditBureauGateway 类 {
                                                                                                                                                                                                无效监听(){...}
                                                                                                                                                                                                void GetCreditScore(CreditBureauRequest quoteRequest,
                                                                                                                                                                                                                  OnCreditReplyEvent OnCreditResponse,
                                                                                                                                                                                                                  对象 ACT) {...}
                                                                                                                                                                                                 ...
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              del­eg­ate void On­CreditReplyEvent(Cred­it­Bur­eauReply creditReply, Object ACT);
                                                                                                                                                                                              class Cred­it­Bur­eau­G­ate­way {
                                                                                                                                                                                                void Listen() {...}
                                                                                                                                                                                                void GetCred­itScore(Cred­it­Bur­eauRe­quest quote­Re­quest,
                                                                                                                                                                                                                  On­CreditReplyEvent On­CreditRe­sponse,
                                                                                                                                                                                                                  Object ACT) {...}
                                                                                                                                                                                                 ...
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              这两个构造都不引用消息或消息队列。这提供了许多好处。首先,我们可以轻松实现完全不依赖消息队列的信用局网关的存根版本(类似于 MockQueue 。其次,如果我们决定使用不同于 MSMQ 的传输方式,我们可以替换信用局网关的实现。例如,如果我们要使用使用 SOAP 和 HTTP 的 Web 服务接口而不是 MSMQ,则网关公开的方法很可能根本不需要更改。

                                                                                                                                                                                              Neither con­struct makes any ref­er­ence to a mes­sage or a mes­sage queue. This provides a number of be­ne­fits. First, we can easily im­ple­ment a stubbed out ver­sion of the credit bureau gate­way that does not rely on mes­sage queues at all (sim­ilar to the Mock­Queue). Second, we can re­place the im­ple­ment­a­tion of the credit bureau gate­way if we decide to use a trans­port dif­fer­ent from MSMQ. For ex­ample, if we were going to use a Web ser­vices in­ter­face using SOAP and HTTP in­stead of MSMQ, the meth­ods ex­posed by the gate­way would most likely not have to change at all.

                                                                                                                                                                                              银行网关

                                                                                                                                                                                              银行网关的设计遵循与信用局网关设计相同的原则。我们使用与之前相同的过程来声明银行指定的请求和回复消息类型的存根。银行网关的外部部分与信用局网关非常相似:

                                                                                                                                                                                              The design of the bank gate­way fol­lows the same prin­ciples as the design of the credit bureau gate­way. We use the same pro­cess as before to de­clare stubs for the re­quest and reply mes­sage types spe­cified by the bank. The ex­ternal por­tion of the bank gate­way is very sim­ilar to the credit bureau gate­way:

                                                                                                                                                                                              delegate void OnBestQuoteEvent(BankQuoteReply bestQuote, Object ACT);
                                                                                                                                                                                              类银行网关{
                                                                                                                                                                                                  无效监听(){...}
                                                                                                                                                                                                  void GetBestQuote(BankQuoteRequest quoteRequest,
                                                                                                                                                                                                                    OnBestQuoteEvent onBestQuoteEvent,
                                                                                                                                                                                                                    对象 ACT) {...}
                                                                                                                                                                                                  ...
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              del­eg­ate void On­Be­stQuoteEvent(BankQuoteReply be­stQuote, Object ACT);
                                                                                                                                                                                              class BankG­ate­way {
                                                                                                                                                                                                  void Listen() {...}
                                                                                                                                                                                                  void Get­Be­stQuote(BankQuote­Re­quest quote­Re­quest,
                                                                                                                                                                                                                    On­Be­stQuoteEvent on­Be­stQuoteEvent,
                                                                                                                                                                                                                    Object ACT) {...}
                                                                                                                                                                                                  ...
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              内部工作稍微复杂一些,因为Scatter-Gather交互方式将单个BankQuoteRequest 路由到多个银行。同样,单个BankQuoteReply通常是多个银行报价回复消息的结果。前一部分由收件人列表处理,而后者由聚合器处理。让我们从收件人列表开始。它必须实现三个主要功能:

                                                                                                                                                                                              The in­ternal work­ings are slightly more com­plex be­cause the Scat­ter-Gather style of in­ter­ac­tion routes a single BankQuote­Re­quest to mul­tiple banks. Like­wise, a single BankQuoteReply is usu­ally the result of mul­tiple bank quote reply mes­sages. The former part is handled by a Re­cip­i­ent List , while the latter is handled by an Ag­greg­ator. Let's start with the Re­cip­i­ent List. It has to im­ple­ment three main func­tions:

                                                                                                                                                                                              • 计算适当的接收者

                                                                                                                                                                                              • Com­pu­ta­tion of ap­pro­pri­ate re­cip­i­ents

                                                                                                                                                                                              • 将消息发送给收件人

                                                                                                                                                                                              • Send­ing the mes­sage to the re­cip­i­ents

                                                                                                                                                                                              • 初始化聚合器以处理传入的回复

                                                                                                                                                                                              • Ini­tial­iz­ing the Ag­greg­ator to pro­cess in­com­ing replies

                                                                                                                                                                                              正如本章介绍中所述,此实现使用Scatter -Gather 的分发方式,主动确定将请求路由到哪些银行。如果银行向经纪人收取每次报价的费用,或者如果银行和经纪人达成协议,要求经纪人对其产生的潜在客户进行资格预审,则这种方法具有商业意义。贷款经纪人根据客户的信用评分、贷款金额和信用记录的长度做出路由决策。我们将与银行的每个连接封装在一个继承自抽象类BankConnection 的类中。此类包含对正确寻址的消息队列的引用和方法CanHandleLoanRequest确定是否应将报价请求转发给该银行。BankConnectionManager只是迭代所有银行连接的列表,并编译符合贷款报价标准的列表。如果银行列表更长,我们可以考虑实施可配置的规则引擎。我们更喜欢当前的方法,因为它简单且明确。

                                                                                                                                                                                              As de­scribed in the in­tro­duc­tion of this chapter, this im­ple­ment­a­tion uses the dis­tri­bu­tion style of Scat­ter-Gather , act­ively de­term­in­ing which banks to route the re­quest to. This ap­proach makes busi­ness sense if the banks charge the broker for each quote or if the bank and the broker have an agree­ment that re­quires the broker to pre­qual­ify leads he or she gen­er­ates. The loan broker makes the rout­ing de­cision based on the cus­tomer's credit score, the amount of the loan and the length of the credit his­tory. We en­cap­su­late each con­nec­tion to a bank inside a class that in­her­its from the ab­stract class Bank­Con­nec­tion. This class con­tains a ref­er­ence to the prop­erly ad­dressed mes­sage queue and a method Can­Handle­L­oan­Re­quest that de­term­ines whether the quote re­quest should be for­war­ded to this bank. The Bank­Con­nec­tion­Man­ager simply it­er­ates through the list of all bank con­nec­tions and com­piles a list of those that match the cri­teria of the loan quote. If the list of banks was longer, we could con­sider im­ple­ment­ing a con­fig­ur­able rules engine. We prefer the cur­rent ap­proach be­cause it is simple and ex­pli­cit.

                                                                                                                                                                                              内部类 BankConnectionManager
                                                                                                                                                                                              {
                                                                                                                                                                                                  静态受保护的 BankConnection[] 银行 =
                                                                                                                                                                                                                   {新银行1(),新银行2(),新银行3(),新银行4(),新银行5()};
                                                                                                                                                                                              
                                                                                                                                                                                                  公共 IMessageSender[] GetEligibleBankQueues
                                                                                                                                                                                                                          (int CreditScore、int HistoryLength、int LoanAmount)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      ArrayList 贷方 = new ArrayList();
                                                                                                                                                                                              
                                                                                                                                                                                                      for (int 索引 = 0; 索引 < 银行.长度; 索引++)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          if (银行[索引].CanHandleLoanRequest(CreditScore, HistoryLength,
                                                                                                                                                                                                                                                贷款额度))
                                                                                                                                                                                                              贷方.Add(银行[index].Queue);
                                                                                                                                                                                                      }
                                                                                                                                                                                                      IMessageSender[] lenderArray = (IMessageSender [])Array.CreateInstance
                                                                                                                                                                                                                                     (typeof(IMessageSender), 贷方.Count);
                                                                                                                                                                                                      贷方.CopyTo(lenderArray);
                                                                                                                                                                                                      返回贷方数组;
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              
                                                                                                                                                                                              
                                                                                                                                                                                              内部抽象类 BankConnection
                                                                                                                                                                                              {
                                                                                                                                                                                                  受保护的 MessageSenderGateway 队列;
                                                                                                                                                                                                  protected String 银行名称 = "";
                                                                                                                                                                                                  公共 MessageSenderGateway 队列
                                                                                                                                                                                                  {
                                                                                                                                                                                                      获取{返回队列; }
                                                                                                                                                                                                  }
                                                                                                                                                                                                  公共字符串银行名称
                                                                                                                                                                                                  {
                                                                                                                                                                                                      获取 { 返回银行名称; }
                                                                                                                                                                                                  }
                                                                                                                                                                                                  public BankConnection(MessageQueue队列)
                                                                                                                                                                                                    { this.queue = new MessageSenderGateway(queue); }
                                                                                                                                                                                                  公共银行连接(字符串队列名称)
                                                                                                                                                                                                    { this.queue = new MessageSenderGateway(queueName); }
                                                                                                                                                                                              
                                                                                                                                                                                                  公共抽象 bool CanHandleLoanRequest(int CreditScore, int HistoryLength,
                                                                                                                                                                                                                                            int 贷款金额);
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              内部类 Bank1 : BankConnection
                                                                                                                                                                                              {
                                                                                                                                                                                                  protected Stringbankname = "独家乡村俱乐部银行家";
                                                                                                                                                                                              
                                                                                                                                                                                                  public Bank1 () : base (".\\private$\\bank1Queue") {}
                                                                                                                                                                                                  公共覆盖 bool CanHandleLoanRequest(int CreditScore, int HistoryLength,
                                                                                                                                                                                                                                            int 贷款金额)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      return LoanAmount >= 75000 && CreditScore >= 600 && HistoryLength >= 8;
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              ...
                                                                                                                                                                                              
                                                                                                                                                                                              in­ternal class Bank­Con­nec­tion­Man­ager
                                                                                                                                                                                              {
                                                                                                                                                                                                  static pro­tec­ted Bank­Con­nec­tion[] banks =
                                                                                                                                                                                                                   {new Bank1(), new Bank2(), new Bank3(), new Bank4(), new Bank5() };
                                                                                                                                                                                              
                                                                                                                                                                                                  public IMes­sageSender[] GetEli­gible­BankQueues
                                                                                                                                                                                                                          (int Cred­itScore, int His­toryLength, int LoanAmount)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      Ar­rayL­ist lenders = new Ar­rayL­ist();
                                                                                                                                                                                              
                                                                                                                                                                                                      for (int index = 0; index < banks.Length; index++)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          if (banks[index].Can­Handle­L­oan­Re­quest(Cred­itScore, His­toryLength,
                                                                                                                                                                                                                                                LoanAmount))
                                                                                                                                                                                                              lenders.Add(banks[index].Queue);
                                                                                                                                                                                                      }
                                                                                                                                                                                                      IMes­sageSender[] lenderAr­ray = (IMes­sageSender [])Array.Cre­ateIn­stance
                                                                                                                                                                                                                                     (typeof(IMes­sageSender), lenders.Count);
                                                                                                                                                                                                      lenders.CopyTo(lenderAr­ray);
                                                                                                                                                                                                      return lenderAr­ray;
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              
                                                                                                                                                                                              
                                                                                                                                                                                              in­ternal ab­stract class Bank­Con­nec­tion
                                                                                                                                                                                              {
                                                                                                                                                                                                  pro­tec­ted Mes­sageSender­Gate­way queue;
                                                                                                                                                                                                  pro­tec­ted String bank­Name = "";
                                                                                                                                                                                                  public Mes­sageSender­Gate­way Queue
                                                                                                                                                                                                  {
                                                                                                                                                                                                      get { return queue; }
                                                                                                                                                                                                  }
                                                                                                                                                                                                  public String Bank­Name
                                                                                                                                                                                                  {
                                                                                                                                                                                                      get { return bank­Name; }
                                                                                                                                                                                                  }
                                                                                                                                                                                                  public Bank­Con­nec­tion (Mes­sageQueue queue)
                                                                                                                                                                                                    { this.queue = new Mes­sageSender­Gate­way(queue); }
                                                                                                                                                                                                  public Bank­Con­nec­tion (String queueName)
                                                                                                                                                                                                    { this.queue = new Mes­sageSender­Gate­way(queueName); }
                                                                                                                                                                                              
                                                                                                                                                                                                  public ab­stract bool Can­Handle­L­oan­Re­quest(int Cred­itScore, int His­toryLength,
                                                                                                                                                                                                                                            int LoanAmount);
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              in­ternal class Bank1 : Bank­Con­nec­tion
                                                                                                                                                                                              {
                                                                                                                                                                                                  pro­tec­ted String bank­name = "Ex­clus­ive Coun­try Club Bankers";
                                                                                                                                                                                              
                                                                                                                                                                                                  public Bank1 () : base (".\\private$\\bank1Queue") {}
                                                                                                                                                                                                  public over­ride bool Can­Handle­L­oan­Re­quest(int Cred­itScore, int His­toryLength,
                                                                                                                                                                                                                                            int LoanAmount)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      return LoanAmount >= 75000 && Cred­itScore >= 600 && His­toryLength >= 8;
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              ...
                                                                                                                                                                                              

                                                                                                                                                                                              一旦编制了相关银行的列表,发送消息就很简单了,只需迭代该列表即可。在生产应用程序中,此迭代应在单个事务内进行,以避免出现错误情况,即消息可能会发送到某些银行,但不会发送到其他银行。我们再次选择让这个例子简单化。

                                                                                                                                                                                              Once the list of rel­ev­ant banks is com­piled, send­ing the mes­sage is a simple matter of it­er­at­ing over the list. In a pro­duc­tion ap­plic­a­tion, this it­er­a­tion should occur inside a single trans­ac­tion to avoid error con­di­tions where a mes­sage may be sent to some banks but not to others. Once again, we chose to let sim­pli­city pre­vail for this ex­ample.

                                                                                                                                                                                              内部类 MessageRouter
                                                                                                                                                                                              {
                                                                                                                                                                                                  公共静态无效SendToRecipientList(消息msg,IMessageSender []收件人列表)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      IEnumerator e =收件人列表.GetEnumerator();
                                                                                                                                                                                                      while (e.MoveNext())
                                                                                                                                                                                                      {
                                                                                                                                                                                                          ((IMessageSender)e.Current).Send(msg);
                                                                                                                                                                                                      }
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              in­ternal class Mes­sageRouter
                                                                                                                                                                                              {
                                                                                                                                                                                                  public static void SendToRe­cip­i­ent­L­ist (Mes­sage msg, IMes­sageSender[] re­cip­i­ent­L­ist)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      IE­nu­mer­ator e = re­cip­i­ent­L­ist.GetE­nu­mer­ator();
                                                                                                                                                                                                      while (e.Move­Next())
                                                                                                                                                                                                      {
                                                                                                                                                                                                          ((IMes­sageSender)e.Cur­rent).Send(msg);
                                                                                                                                                                                                      }
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              现在请求消息已发送至银行,我们需要初始化聚合器以接收传入的银行报价。由于贷款经纪人的事件驱动性质,聚合器需要准备好处理多个聚合,同时为每个待处理的报价请求维护一个活动聚合。这意味着传入消息需要与特定聚合唯一相关。不幸的是,我们不能使用消息 ID 作为关联标识符,因为收件人列表需要向每个银行发送单独的消息。因此,如果三个银行参与报价请求,则接收者列表需要发送三条独特的消息,每条消息发送给每家银行。这些消息中的每条消息都将具有唯一的消息 ID,因此,如果银行要通过消息 ID 进行关联,则三个响应将具有不同的关联 ID,即使它们属于同一聚合。这将使聚合器无法识别相关消息。我们可以让聚合存储每个请求消息的消息 ID,从而将传入消息的相关 ID 关联回聚合。然而,这比我们需要的更复杂。相反,我们只是为每个聚合生成我们自己的相关 IDsone,而不是为每条消息生成一个相关 IDsone。我们将此(数字)ID 存储在AppSpecific中传出请求消息的属性。银行继承自RequestReplyService ,它已经将传入消息的AppSpecific 属性传输到回复消息。当报价消息从银行传入时,BankGateway 可以通过消息的AppSpecific(见图)。

                                                                                                                                                                                              Now that re­quest mes­sages are on their way to the banks, we need to ini­tial­ize the Ag­greg­ator to expect in­com­ing bank quotes. Due to the event-driven nature of the loan broker, the ag­greg­ator needs to be pre­pared to work on more than one ag­greg­ate con­cur­rently­main­tain­ing one active ag­greg­ate for each quote re­quest that is pending. This means that in­com­ing mes­sages need to be uniquely cor­rel­ated to a spe­cific ag­greg­ate. Un­for­tu­nately, we cannot use the mes­sage ID as the cor­rel­a­tion iden­ti­fier be­cause the Re­cip­i­ent List needs to send in­di­vidual mes­sages to each of the banks. As a result, if three banks par­ti­cip­ate in a quote re­quest, the Re­cip­i­ent List needs to send three unique mes­sages, one to each bank. Each of these mes­sages will have a unique mes­sage ID, so if the banks were to cor­rel­ate by mes­sage ID, the three re­sponses will have dif­fer­ent cor­rel­a­tion IDs even though they belong to the same ag­greg­ate. This would make it im­pos­sible for the ag­greg­ator to identify re­lated mes­sages. We could have the ag­greg­ate store the mes­sage ID for each re­quest mes­sage and thus cor­rel­ate the in­com­ing mes­sage's cor­rel­a­tion ID back to the ag­greg­ate. How­ever, this is more com­plic­ated than we need it to be. In­stead, we just gen­er­ate our own cor­rel­a­tion IDsone for each ag­greg­ate as op­posed to one for each mes­sage. We store this (nu­meric) ID in the AppSpe­cific prop­erty of the out­go­ing re­quest mes­sages. The banks in­herit from Re­questReplyS­er­vice, which already trans­fers the in­com­ing mes­sage's AppSpe­cific prop­erty to the reply mes­sages. When a quote mes­sage comes in from a bank, the BankG­ate­way can easily cor­rel­ate the in­com­ing mes­sage by the AppSpe­cific prop­erty of the mes­sage (see figure).

                                                                                                                                                                                              BankGateway 使用 AppSpecific 消息属性将响应消息与聚合相关联

                                                                                                                                                                                              The BankG­ate­way Uses the AppSpe­cific Mes­sage Prop­erty to Cor­rel­ate Re­sponse Mes­sages to Ag­greg­ates

                                                                                                                                                                                              图形/09inf20.gif

                                                                                                                                                                                              银行网关使用聚合 ID(由简单计数器生成)和预期消息数来初始化聚合。此外,调用者需要提供委托,并且可以选择指定对 ACT 的对象引用,就像信用局网关的工作方式一样。聚合策略很简单。当所有选定的银行都回复了回复消息时,汇总即被视为完成。收件人名单使用预期消息的数量初始化聚合。请记住,银行可以选择拒绝提供报价。为了让我们知道聚合何时完成,如果银行不想提供报价,我们要求银行提供包含错误代码集的回复消息。我们可以很容易地修改聚合策略,例如,在 1 秒后停止竞价,并采取迄今为止最好的响应。

                                                                                                                                                                                              The bank gate­way ini­tial­izes an ag­greg­ate with the ag­greg­ate ID (gen­er­ated by a simple counter) and the number of ex­pec­ted mes­sages. In ad­di­tion, the caller needs to supply a del­eg­ate and can op­tion­ally spe­cify an object ref­er­ence to an ACT in the same way the credit bureau gate­way func­tioned. The ag­greg­a­tion strategy is simple. The ag­greg­ate is con­sidered com­plete when all se­lec­ted banks have re­spon­ded with a reply mes­sage. The Re­cip­i­ent List ini­tial­izes the ag­greg­ate with the number of ex­pec­ted mes­sages. Re­mem­ber that banks have the option of de­clin­ing to provide a quote. So that we can know when an ag­greg­ate is com­plete, we re­quire the banks to provide a reply mes­sage with the error code set if they do not want to provide a quote. We could easily modify the ag­greg­a­tion strategy, for ex­ample, to cut off the bid­ding after 1 second and take the best re­sponse up to that point.

                                                                                                                                                                                              内部类 BankQuoteAggregate
                                                                                                                                                                                              {
                                                                                                                                                                                                  受保护的 int ID;
                                                                                                                                                                                                  受保护的int预期消息;
                                                                                                                                                                                                  受保护对象 ACT;
                                                                                                                                                                                                  受保护的 OnBestQuoteEvent 回调;
                                                                                                                                                                                              
                                                                                                                                                                                                  受保护的双倍最佳率 = 0.0;
                                                                                                                                                                                              
                                                                                                                                                                                                  protected ArrayList receiveMessages = new ArrayList();
                                                                                                                                                                                                  受保护的 BankQuoteReply bestReply = null;
                                                                                                                                                                                              
                                                                                                                                                                                                  公共 BankQuoteAggregate(int ID, int ExpectedMessages, OnBestQuoteEvent 回调,
                                                                                                                                                                                                                            对象行动)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      这个.ID = ID;
                                                                                                                                                                                                      this.expectedMessages = ExpectedMessages;
                                                                                                                                                                                                      this.callback = 回调;
                                                                                                                                                                                                      这个.ACT = ACT;
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  公共无效AddMessage(BankQuoteReply回复)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      if (回复.ErrorCode == 0)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          if (bestReply == null)
                                                                                                                                                                                                          {
                                                                                                                                                                                                              最佳回复=回复;
                                                                                                                                                                                                          }
                                                                                                                                                                                                          别的
                                                                                                                                                                                                          {
                                                                                                                                                                                                              if (reply.InterestRate < bestReply.InterestRate)
                                                                                                                                                                                                              {
                                                                                                                                                                                                                  最佳回复=回复;
                                                                                                                                                                                                              }
                                                                                                                                                                                                          }
                                                                                                                                                                                                      }
                                                                                                                                                                                                      receiveMessages.Add(回复);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  公共 bool IsComplete()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      返回 receiveMessages.Count == ExpectedMessages;
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  公共 BankQuoteReply getBestResult()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      返回最佳回复;
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  公共无效NotifyBestResult()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      if(回调!= null)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          回调(bestReply,ACT);
                                                                                                                                                                                                      }
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              in­ternal class BankQuoteAg­greg­ate
                                                                                                                                                                                              {
                                                                                                                                                                                                  pro­tec­ted int ID;
                                                                                                                                                                                                  pro­tec­ted int ex­pec­tedMes­sages;
                                                                                                                                                                                                  pro­tec­ted Object ACT;
                                                                                                                                                                                                  pro­tec­ted On­Be­stQuoteEvent call­back;
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted double be­stRate = 0.0;
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted Ar­rayL­ist re­ceivedMes­sages = new Ar­rayL­ist();
                                                                                                                                                                                                  pro­tec­ted BankQuoteReply be­stReply = null;
                                                                                                                                                                                              
                                                                                                                                                                                                  public BankQuoteAg­greg­ate(int ID, int ex­pec­tedMes­sages, On­Be­stQuoteEvent call­back,
                                                                                                                                                                                                                            Object ACT)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      this.ID = ID;
                                                                                                                                                                                                      this.ex­pec­tedMes­sages = ex­pec­tedMes­sages;
                                                                                                                                                                                                      this.call­back = call­back;
                                                                                                                                                                                                      this.ACT = ACT;
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  public void AddMes­sage(BankQuoteReply reply)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      if (reply.Er­ror­Code == 0)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          if (be­stReply == null)
                                                                                                                                                                                                          {
                                                                                                                                                                                                              be­stReply = reply;
                                                                                                                                                                                                          }
                                                                                                                                                                                                          else
                                                                                                                                                                                                          {
                                                                                                                                                                                                              if (reply.In­teres­tRate < be­stReply.In­teres­tRate)
                                                                                                                                                                                                              {
                                                                                                                                                                                                                  be­stReply = reply;
                                                                                                                                                                                                              }
                                                                                                                                                                                                          }
                                                                                                                                                                                                      }
                                                                                                                                                                                                      re­ceivedMes­sages.Add(reply);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  public bool IsCom­plete()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      return re­ceivedMes­sages.Count == ex­pec­tedMes­sages;
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  public BankQuoteReply get­Be­stRes­ult()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      return be­stReply;
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  public void No­ti­fy­Be­stRes­ult()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      if (call­back != null)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          call­back(be­stReply, ACT);
                                                                                                                                                                                                      }
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              有了银行连接管理器、收件人列表和聚合, BankGateway 的功能实现就变得相对简单:

                                                                                                                                                                                              Armed with the bank con­nec­tion man­ager, the re­cip­i­ent list, and the ag­greg­ate, the im­ple­ment­a­tion of the BankG­ate­way's func­tions be­comes re­l­at­ively simple:

                                                                                                                                                                                              银行网关.cs
                                                                                                                                                                                              内部类BankGateway
                                                                                                                                                                                                {
                                                                                                                                                                                                    受保护的 IMessageReceiver 银行回复队列;
                                                                                                                                                                                                    受保护的 BankConnectionManager 连接管理器;
                                                                                                                                                                                              
                                                                                                                                                                                                    受保护的 IDictionaryaggregateBuffer = (IDictionary)(new Hashtable());
                                                                                                                                                                                                    受保护的 int 聚合CorrelationID;
                                                                                                                                                                                              
                                                                                                                                                                                                    公共无效监听()
                                                                                                                                                                                                    {
                                                                                                                                                                                                        银行ReplyQueue.Begin();
                                                                                                                                                                                                    }
                                                                                                                                                                                              
                                                                                                                                                                                                    公共无效GetBestQuote(BankQuoteRequest quoteRequest,
                                                                                                                                                                                                                             OnBestQuoteEvent onBestQuoteEvent,对象 ACT)
                                                                                                                                                                                                    {
                                                                                                                                                                                              
                                                                                                                                                                                                        消息 requestMessage = new Message(quoteRequest);
                                                                                                                                                                                                        requestMessage.AppSpecific = AggregationCorrelationID;
                                                                                                                                                                                                        requestMessage.ResponseQueue =bankReplyQueue.GetQueue();
                                                                                                                                                                                                        IMessageSender[] 合格银行 =
                                                                                                                                                                                                            connectionManager.GetEligibleBankQueues(quoteRequest.CreditScore,
                                                                                                                                                                                                                                                    quoteRequest.HistoryLength,
                                                                                                                                                                                                                                                    quoteRequest.LoanAmount);
                                                                                                                                                                                              
                                                                                                                                                                                                        aggregateBuffer.Add(aggregationCorrelationID,
                                                                                                                                                                                                            新的 BankQuoteAggregate(aggregationCorrelationID, 有资格的Banks.Length,
                                                                                                                                                                                                                                   onBestQuoteEvent,ACT));
                                                                                                                                                                                                        聚合CorrelationID++;
                                                                                                                                                                                              
                                                                                                                                                                                                        MessageRouter.SendToRecipientList(requestMessage,合格银行);
                                                                                                                                                                                                    }
                                                                                                                                                                                              
                                                                                                                                                                                              
                                                                                                                                                                                                    私人无效OnBankMessage(消息msg)
                                                                                                                                                                                                    {
                                                                                                                                                                                                        msg.Formatter = GetFormatter();
                                                                                                                                                                                              
                                                                                                                                                                                                        BankQuoteReply回复结构;
                                                                                                                                                                                                        尝试
                                                                                                                                                                                                        {
                                                                                                                                                                                                            if (msg.Body 是 BankQuoteReply)
                                                                                                                                                                                                            {
                                                                                                                                                                                                                回复结构 = (BankQuoteReply)msg.Body;
                                                                                                                                                                                                                int AggregationCorrelationID = msg.AppSpecific;
                                                                                                                                                                                                                Console.WriteLine("引用 {0:0.00}% {1} {2}",
                                                                                                                                                                                                                                  回复结构.InterestRate, 回复结构.QuoteID,
                                                                                                                                                                                                                                  回复结构.ErrorCode);
                                                                                                                                                                                                                if (aggregateBuffer.Contains(aggregationCorrelationID))
                                                                                                                                                                                                                {
                                                                                                                                                                                                                    BankQuoteAggregate 合计 =
                                                                                                                                                                                                                       (BankQuoteAggregate)(aggregateBuffer[aggregationCorrelationID]);
                                                                                                                                                                                                                    聚合.AddMessage(replyStruct);
                                                                                                                                                                                                                    if (聚合.IsComplete())
                                                                                                                                                                                                                    {
                                                                                                                                                                                                                        聚合.NotifyBestResult();
                                                                                                                                                                                                                        aggregateBuffer.Remove(aggregationCorrelationID);
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                }
                                                                                                                                                                                                                别的
                                                                                                                                                                                                                { Console.WriteLine("收款银行响应与任何
                                                                                                                                                                                              图形/ccc.gif总计的”); }
                                                                                                                                                                                                            }
                                                                                                                                                                                                            别的
                                                                                                                                                                                                            { Console.WriteLine("非法请求。"); }
                                                                                                                                                                                                        }
                                                                                                                                                                                                        捕获(异常 e)
                                                                                                                                                                                                        {
                                                                                                                                                                                                            Console.WriteLine("异常:{0}", e.ToString());
                                                                                                                                                                                                        }
                                                                                                                                                                                                    }
                                                                                                                                                                                                }
                                                                                                                                                                                              
                                                                                                                                                                                              in­ternal class BankG­ate­way
                                                                                                                                                                                                {
                                                                                                                                                                                                    pro­tec­ted IMes­sageRe­ceiver bankReplyQueue;
                                                                                                                                                                                                    pro­tec­ted Bank­Con­nec­tion­Man­ager con­nec­tion­Man­ager;
                                                                                                                                                                                              
                                                                                                                                                                                                    pro­tec­ted IDic­tion­ary ag­greg­ate­Buf­fer = (IDic­tion­ary)(new Hasht­able());
                                                                                                                                                                                                    pro­tec­ted int ag­greg­a­tion­Cor­rel­a­tionID;
                                                                                                                                                                                              
                                                                                                                                                                                                    public void Listen()
                                                                                                                                                                                                    {
                                                                                                                                                                                                        bankReplyQueue.Begin();
                                                                                                                                                                                                    }
                                                                                                                                                                                              
                                                                                                                                                                                                    public void Get­Be­stQuote(BankQuote­Re­quest quote­Re­quest,
                                                                                                                                                                                                                             On­Be­stQuoteEvent on­Be­stQuoteEvent, Object ACT)
                                                                                                                                                                                                    {
                                                                                                                                                                                              
                                                                                                                                                                                                        Mes­sage re­quest­Mes­sage = new Mes­sage(quote­Re­quest);
                                                                                                                                                                                                        re­quest­Mes­sage.AppSpe­cific = ag­greg­a­tion­Cor­rel­a­tionID;
                                                                                                                                                                                                        re­quest­Mes­sage.Re­spon­se­Queue = bankReplyQueue.GetQueue();
                                                                                                                                                                                                        IMes­sageSender[] eli­gible­Banks =
                                                                                                                                                                                                            con­nec­tion­Man­ager.GetEli­gible­BankQueues(quote­Re­quest.Cred­itScore,
                                                                                                                                                                                                                                                    quote­Re­quest.His­toryLength,
                                                                                                                                                                                                                                                    quote­Re­quest.LoanAmount);
                                                                                                                                                                                              
                                                                                                                                                                                                        ag­greg­ate­Buf­fer.Add(ag­greg­a­tion­Cor­rel­a­tionID,
                                                                                                                                                                                                            new BankQuoteAg­greg­ate(ag­greg­a­tion­Cor­rel­a­tionID, eli­gible­Banks.Length,
                                                                                                                                                                                                                                   on­Be­stQuoteEvent, ACT));
                                                                                                                                                                                                        ag­greg­a­tion­Cor­rel­a­tionID++;
                                                                                                                                                                                              
                                                                                                                                                                                                        Mes­sageRouter.SendToRe­cip­i­ent­L­ist(re­quest­Mes­sage, eli­gible­Banks);
                                                                                                                                                                                                    }
                                                                                                                                                                                              
                                                                                                                                                                                              
                                                                                                                                                                                                    private void On­BankMes­sage(Mes­sage msg)
                                                                                                                                                                                                    {
                                                                                                                                                                                                        msg.Format­ter =  Get­Format­ter();
                                                                                                                                                                                              
                                                                                                                                                                                                        BankQuoteReply reply­S­truct;
                                                                                                                                                                                                        try
                                                                                                                                                                                                        {
                                                                                                                                                                                                            if (msg.Body is BankQuoteReply)
                                                                                                                                                                                                            {
                                                                                                                                                                                                                reply­S­truct = (BankQuoteReply)msg.Body;
                                                                                                                                                                                                                int ag­greg­a­tion­Cor­rel­a­tionID = msg.AppSpe­cific;
                                                                                                                                                                                                                Con­sole.WriteLine("Quote {0:0.00}% {1} {2}",
                                                                                                                                                                                                                                  reply­S­truct.In­teres­tRate, reply­S­truct.QuoteID,
                                                                                                                                                                                                                                  reply­S­truct.Er­ror­Code);
                                                                                                                                                                                                                if (ag­greg­ate­Buf­fer.Con­tains(ag­greg­a­tion­Cor­rel­a­tionID))
                                                                                                                                                                                                                {
                                                                                                                                                                                                                    BankQuoteAg­greg­ate ag­greg­ate =
                                                                                                                                                                                                                       (BankQuoteAg­greg­ate)(ag­greg­ate­Buf­fer[ag­greg­a­tion­Cor­rel­a­tionID]);
                                                                                                                                                                                                                    ag­greg­ate.AddMes­sage(reply­S­truct);
                                                                                                                                                                                                                    if (ag­greg­ate.IsCom­plete())
                                                                                                                                                                                                                    {
                                                                                                                                                                                                                        ag­greg­ate.No­ti­fy­Be­stRes­ult();
                                                                                                                                                                                                                        ag­greg­ate­Buf­fer.Remove(ag­greg­a­tion­Cor­rel­a­tionID);
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                }
                                                                                                                                                                                                                else
                                                                                                                                                                                                                { Con­sole.WriteLine("In­com­ing bank re­sponse does not match any
                                                                                                                                                                                               ag­greg­ate"); }
                                                                                                                                                                                                            }
                                                                                                                                                                                                            else
                                                                                                                                                                                                            { Con­sole.WriteLine("Il­legal re­quest."); }
                                                                                                                                                                                                        }
                                                                                                                                                                                                        catch (Ex­cep­tion e)
                                                                                                                                                                                                        {
                                                                                                                                                                                                            Con­sole.WriteLine("Ex­cep­tion: {0}", e.To­String());
                                                                                                                                                                                                        }
                                                                                                                                                                                                    }
                                                                                                                                                                                                }
                                                                                                                                                                                              

                                                                                                                                                                                              当银行网关收到银行的报价回复时,OnBankMessage方法就会执行。该方法将传入消息转换为正确的类型,并继续通过AppSpecific属性查找相关聚合。它将新的出价添加到总计中。一旦聚合完成(如BankQuoteAggregate类中所定义), BankGateway 将调用调用者提供的委托。

                                                                                                                                                                                              When the bank gate­way re­ceives a quote reply from a bank, the On­BankMes­sage method ex­ecutes. The method con­verts the in­com­ing mes­sage to the cor­rect type and goes on to locate the re­lated ag­greg­ate via the AppSpe­cific prop­erty. It adds the new bid to the ag­greg­ate. Once the ag­greg­ate is com­plete (as defined in the BankQuoteAg­greg­ate class), the BankG­ate­way in­vokes the del­eg­ate sup­plied by the caller.

                                                                                                                                                                                              接受请求

                                                                                                                                                                                              现在我们有了封装良好的信用局网关和银行网关,我们已准备好让贷款经纪人接受请求。前面,我们讨论了MQServiceAsyncRequestReplyService 基类的设计。LoanBroker类继承自AsyncRequestReplyService,因为它不能立即将结果发送回回复队列,而只能在其他几个异步操作(获取信用存储并与银行通信)完成后才将结果发送回回复队列。

                                                                                                                                                                                              Now that we have a well-en­cap­su­lated credit bureau gate­way and bank gate­way, we are ready to have the loan broker accept re­quests. Earlier, we dis­cussed the design of the MQSer­vice and Asyn­cRe­questReplyS­er­vice base classes. The Loan­Broker class in­her­its from Asyn­cRe­questReplyS­er­vice be­cause it cannot send the res­ults back to the reply queue right away, but only after sev­eral other asyn­chron­ous op­er­a­tions (ob­tain­ing the credit store and com­mu­nic­at­ing with the banks) com­plete.

                                                                                                                                                                                              实现 LoanBroker 的第一步定义贷款经纪人处理的消息类型:

                                                                                                                                                                                              The first step in im­ple­ment­ing the Loan­Broker is to define the mes­sage types the loan broker handles:

                                                                                                                                                                                              公共结构 LoanQuoteRequest
                                                                                                                                                                                              {
                                                                                                                                                                                                  公共 int SSN;
                                                                                                                                                                                                  公共双倍贷款金额;
                                                                                                                                                                                                  公共 int LoanTerm;
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              公共结构 LoanQuoteReply
                                                                                                                                                                                              {
                                                                                                                                                                                                  公共 int SSN;
                                                                                                                                                                                                  公共双倍贷款金额;
                                                                                                                                                                                                  公共双倍利率;
                                                                                                                                                                                                  公共字符串QuoteID;
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              public struct Loan­Quote­Re­quest
                                                                                                                                                                                              {
                                                                                                                                                                                                  public int SSN;
                                                                                                                                                                                                  public double LoanAmount;
                                                                                                                                                                                                  public int LoanTerm;
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              public struct Loan­QuoteReply
                                                                                                                                                                                              {
                                                                                                                                                                                                  public int SSN;
                                                                                                                                                                                                  public double LoanAmount;
                                                                                                                                                                                                  public double In­teres­tRate;
                                                                                                                                                                                                  public string QuoteID;
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              接下来,我们创建一个继承自AsyncRequestReplyService 的类并重写ProcessMessage 方法

                                                                                                                                                                                              Next, we create a class that in­her­its from Asyn­cRe­questReplyS­er­vice and over­ride the Pro­cess­Mes­sage method.

                                                                                                                                                                                              过程

                                                                                                                                                                                              贷款经纪人与前面的类不同,因为传入消息触发的过程不包含在任何单个方法中。相反,该过程的完成取决于一系列外部事件。贷款经纪人可以接收三种类型的事件:

                                                                                                                                                                                              The loan broker is dif­fer­ent from the pre­vi­ous classes be­cause the pro­cess that is triggered by an in­com­ing mes­sage is not con­tained in any single method. In­stead, the com­ple­tion of the pro­cess de­pends on a se­quence of ex­ternal events. The loan broker can re­ceive three types of events:

                                                                                                                                                                                              • 收到新的贷款请求消息。

                                                                                                                                                                                              • A new loan re­quest mes­sage ar­rives.

                                                                                                                                                                                              • 信用评分回复消息到达(通过 CreditBureauGateway

                                                                                                                                                                                              • A credit score reply mes­sage ar­rives (via the Cred­it­Bur­eau­G­ate­way).

                                                                                                                                                                                              • 银行报价消息到达(通过 BankGateway

                                                                                                                                                                                              • A bank quote mes­sage ar­rives (via the BankG­ate­way).

                                                                                                                                                                                              由于贷款经纪人的逻辑分布在多个事件处理程序中,因此我们需要在这些函数中保持经纪人的状态。这就是异步完成令牌的用武之地!请记住,信用局网关和银行网关允许调用者(贷款经纪人)在发送请求时传递对对象实例的引用。当网关收到回复消息时,将对象引用传回。为了利用此功能,我们在贷款经纪人中声明 ACT,如下所示:

                                                                                                                                                                                              Since the logic for the loan broker is spread across mul­tiple event hand­lers, we need to keep the state of the broker across these func­tions. That's where the asyn­chron­ous com­ple­tion tokens come in! Re­mem­ber that the credit bureau gate­way and the bank gate­way allow the caller (the loan broker) to pass a ref­er­ence to an object in­stance when send­ing a re­quest. The gate­way passes the object ref­er­ence back when the reply mes­sage is re­ceived. To take ad­vant­age of this func­tion­al­ity, we de­clare an ACT in the loan broker as fol­lows:

                                                                                                                                                                                              内部类ACT
                                                                                                                                                                                              {
                                                                                                                                                                                                  公共贷款报价请求贷款请求;
                                                                                                                                                                                                  公共留言留言;
                                                                                                                                                                                              
                                                                                                                                                                                                  公共ACT(LoanQuoteRequest贷款请求,消息消息)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      this.loanRequest = 贷款请求;
                                                                                                                                                                                                      this.message = 消息;
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              in­ternal class ACT
                                                                                                                                                                                              {
                                                                                                                                                                                                  public Loan­Quote­Re­quest loan­Re­quest;
                                                                                                                                                                                                  public Mes­sage mes­sage;
                                                                                                                                                                                              
                                                                                                                                                                                                  public ACT(Loan­Quote­Re­quest loan­Re­quest, Mes­sage mes­sage)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      this.loan­Re­quest = loan­Re­quest;
                                                                                                                                                                                                      this.mes­sage = mes­sage;
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              ACT 包含原始请求消息的副本(其中包含创建回复消息所需的消息 ID 和回复地址)和请求数据结构(需要将 SSN 和贷款金额复制到回复消息中)。从技术上讲,ACT存储了少量的重复信息,因为我们可以从请求消息中提取请求结构的内容。然而,访问强类型结构的便利性值得花费一些额外的字节。

                                                                                                                                                                                              The ACT con­tains a copy of the ori­ginal re­quest mes­sage (which con­tains the mes­sage ID and the reply ad­dress re­quired to create the reply mes­sage) and the re­quest data struc­ture (needed to copy the SSN and the loan amount into the reply mes­sage). Tech­nic­ally speak­ing, the ACT stores a small amount of du­plic­ate in­form­a­tion be­cause we could ex­tract the con­tent of the re­quest struc­ture from the re­quest mes­sage. How­ever, the con­veni­ence of ac­cess­ing a strongly typed struc­ture is worth the few extra bytes.

                                                                                                                                                                                              贷款经纪人的其余部分实施如下:

                                                                                                                                                                                              The re­mainder of the loan broker is im­ple­men­ted as fol­lows:

                                                                                                                                                                                              贷款经纪人.cs
                                                                                                                                                                                              内部类 LoanBroker :AsyncRequestReplyService
                                                                                                                                                                                              {
                                                                                                                                                                                                  受保护的 ICreditBureauGateway 信用局接口;
                                                                                                                                                                                                  受保护的银行网关银行接口;
                                                                                                                                                                                              
                                                                                                                                                                                                  公共贷款经纪人(字符串请求队列名称,
                                                                                                                                                                                                                    字符串creditRequestQueueName,字符串creditReplyQueueName,
                                                                                                                                                                                                                    字符串银行回复队列名称,
                                                                                                                                                                                                                    BankConnectionManager 连接管理器):base(requestQueueName)
                                                                                                                                                                                                  {
                                                                                                                                                                                              
                                                                                                                                                                                                      CreditBureauInterface = (ICCreditBureauGateway)
                                                                                                                                                                                                          (new CreditBureauGatewayImp(creditRequestQueueName, CreditReplyQueueName));
                                                                                                                                                                                                      CreditBureauInterface.Listen();
                                                                                                                                                                                              
                                                                                                                                                                                                      BankInterface = new BankGateway(bankReplyQueueName, 连接管理器);
                                                                                                                                                                                                      银行接口.Listen();
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  受保护的覆盖类型 GetRequestBodyType()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      返回类型(LoanQuoteRequest);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  protected override void ProcessMessage(对象o,消息msg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      贷款报价请求报价请求;
                                                                                                                                                                                                      quoteRequest = (LoanQuoteRequest)o;
                                                                                                                                                                                              
                                                                                                                                                                                                      CreditBureauRequest 信用请求 =
                                                                                                                                                                                                          LoanBrokerTranslator.GetCreditBureaurequest(quoteRequest);
                                                                                                                                                                                              
                                                                                                                                                                                                      ACT act = new ACT(quoteRequest, msg);
                                                                                                                                                                                              
                                                                                                                                                                                                      CreditBureauInterface.GetCreditScore(creditRequest,
                                                                                                                                                                                                                                           新的 OnCreditReplyEvent(OnCreditReply), act);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  private void OnCreditReply(CreditBureauReply CreditReply, Object act)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      ACT myAct = (ACT) 行动;
                                                                                                                                                                                              
                                                                                                                                                                                                      Console.WriteLine("收到的信用评分 -- SSN {0} 评分 {1} 长度 {2}",
                                                                                                                                                                                                                        CreditReply.SSN、creditReply.CreditScore、
                                                                                                                                                                                                                        CreditReply.HistoryLength);
                                                                                                                                                                                                      银行报价请求 银行请求 =
                                                                                                                                                                                                          LoanBrokerTranslator.GetBankQuoteRequest(myAct.loanRequest ,creditReply);
                                                                                                                                                                                                      BankInterface.GetBestQuote(bankRequest, new OnBestQuoteEvent(OnBestQuote), act);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  private void OnBestQuote(BankQuoteReply bestQuote, Object act)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      ACT myAct = (ACT) 行动;
                                                                                                                                                                                              
                                                                                                                                                                                                      LoanQuoteReply quoteReply = LoanBrokerTranslator.GetLoanQuoteReply
                                                                                                                                                                                                                                  (myAct.loanRequest, bestQuote);
                                                                                                                                                                                                      Console.WriteLine("最佳报价{0} {1}",
                                                                                                                                                                                                                        quoteReply.InterestRate, quoteReply.QuoteID);
                                                                                                                                                                                                      SendReply(quoteReply, myAct.message);
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              in­ternal class Loan­Broker : Asyn­cRe­questReplyS­er­vice
                                                                                                                                                                                              {
                                                                                                                                                                                                  pro­tec­ted ICred­it­Bur­eau­G­ate­way cred­it­Bur­eau­In­ter­face;
                                                                                                                                                                                                  pro­tec­ted BankG­ate­way bank­In­ter­face;
                                                                                                                                                                                              
                                                                                                                                                                                                  public Loan­Broker(String re­questQueueName,
                                                                                                                                                                                                                    String creditRe­questQueueName, String creditReplyQueueName,
                                                                                                                                                                                                                    String bankReplyQueueName,
                                                                                                                                                                                                                    Bank­Con­nec­tion­Man­ager con­nec­tion­Man­ager): base(re­questQueueName)
                                                                                                                                                                                                  {
                                                                                                                                                                                              
                                                                                                                                                                                                      cred­it­Bur­eau­In­ter­face = (ICred­it­Bur­eau­G­ate­way)
                                                                                                                                                                                                          (new Cred­it­Bur­eau­G­ate­way­Imp(creditRe­questQueueName, creditReplyQueueName));
                                                                                                                                                                                                      cred­it­Bur­eau­In­ter­face.Listen();
                                                                                                                                                                                              
                                                                                                                                                                                                      bank­In­ter­face = new BankG­ate­way(bankReplyQueueName, con­nec­tion­Man­ager);
                                                                                                                                                                                                      bank­In­ter­face.Listen();
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted over­ride Type GetRe­quest­Bo­dy­Type()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      return typeof(Loan­Quote­Re­quest);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted over­ride void Pro­cess­Mes­sage(Object o, Mes­sage msg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      Loan­Quote­Re­quest quote­Re­quest;
                                                                                                                                                                                                      quote­Re­quest = (Loan­Quote­Re­quest)o;
                                                                                                                                                                                              
                                                                                                                                                                                                      Cred­it­Bur­eauRe­quest creditRe­quest =
                                                                                                                                                                                                          Loan­Broker­Trans­lator.GetCred­it­Bur­eaure­quest(quote­Re­quest);
                                                                                                                                                                                              
                                                                                                                                                                                                      ACT act = new ACT(quote­Re­quest, msg);
                                                                                                                                                                                              
                                                                                                                                                                                                      cred­it­Bur­eau­In­ter­face.GetCred­itScore(creditRe­quest,
                                                                                                                                                                                                                                           new On­CreditReplyEvent(On­CreditReply), act);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  private void On­CreditReply(Cred­it­Bur­eauReply creditReply, Object act)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      ACT myAct = (ACT)act;
                                                                                                                                                                                              
                                                                                                                                                                                                      Con­sole.WriteLine("Re­ceived Credit Score -- SSN {0} Score {1} Length {2}",
                                                                                                                                                                                                                        creditReply.SSN, creditReply.Cred­itScore,
                                                                                                                                                                                                                        creditReply.His­toryLength);
                                                                                                                                                                                                      BankQuote­Re­quest bankRe­quest =
                                                                                                                                                                                                          Loan­Broker­Trans­lator.Get­BankQuote­Re­quest(myAct.loan­Re­quest ,creditReply);
                                                                                                                                                                                                      bank­In­ter­face.Get­Be­stQuote(bankRe­quest, new On­Be­stQuoteEvent(On­Be­stQuote), act);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  private void On­Be­stQuote(BankQuoteReply be­stQuote, Object act)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      ACT myAct = (ACT)act;
                                                                                                                                                                                              
                                                                                                                                                                                                      Loan­QuoteReply quoteReply = Loan­Broker­Trans­lator.GetLoan­QuoteReply
                                                                                                                                                                                                                                  (myAct.loan­Re­quest, be­stQuote);
                                                                                                                                                                                                      Con­sole.WriteLine("Best quote {0} {1}",
                                                                                                                                                                                                                        quoteReply.In­teres­tRate, quoteReply.QuoteID);
                                                                                                                                                                                                      SendReply(quoteReply, myAct.mes­sage);
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              LoanBroker继承自AsyncRequestReplyService ,它提供对接收请求和发送相关回复的支持。LoanBroker重写ProcessMessage 方法来处理传入的请求消息。ProcessMessage创建ACT 的新实例并调用信用局网关来请求信用评分。有趣的是,该方法到此结束。当信用局网关调用OnCreditReply (ProcessMessage时,处理继续方法。此方法使用 ACT 和信用局回复来创建银行报价请求,并调用银行网关发送请求消息。这次它指定方法OnBestQuote作为回调委托。一旦银行网关收到所有银行报价回复,它就会通过委托调用此方法并传回 ACT 实例。OnBestQuote 使用银行报价和 ACT 创建对客户的回复,并使用SendReply

                                                                                                                                                                                              Loan­Broker in­her­its from Asyn­cRe­questReplyS­er­vice, which provides sup­port for re­ceiv­ing re­quests and send­ing cor­rel­ated replies. Loan­Broker over­rides the method Pro­cess­Mes­sage to deal with in­com­ing re­quest mes­sages. Pro­cess­Mes­sage cre­ates a new in­stance of the ACT and calls the credit bureau gate­way to re­quest a credit score. In­ter­est­ingly, the method ends there. Pro­cess­ing con­tin­ues when the credit bureau gate­way in­vokes On­CreditReply, the del­eg­ate spe­cified by the Pro­cess­Mes­sage method. This method uses the ACT and the credit bureau reply to create a bank quote re­quest and calls the bank gate­way to send the re­quest mes­sages. This time it spe­cifies the method On­Be­stQuote as the call­back del­eg­ate. Once the bank gate­way re­ceives all bank quote replies, it in­vokes this method via the del­eg­ate and passes back the in­stance of the ACT. On­Be­stQuote uses the bank quote and the ACT to create a reply to the cus­tomer and sends it off using the base class im­ple­ment­a­tion of SendReply.

                                                                                                                                                                                              您可能在源代码中注意到的一类是LoanBrokerTranslator 。此类提供了一些静态方法,有助于在不同消息格式之间进行转换。

                                                                                                                                                                                              One class you prob­ably no­ticed in the source code is the Loan­Broker­Trans­lator. This class provides a hand­ful of static meth­ods that help con­vert between the dif­fer­ent mes­sage formats.

                                                                                                                                                                                              LoanBroker类演示了我们在设计中所做的权衡。该代码没有引用消息传递或线程相关的概念(除了从AsyncRequestReplyService 继承),这使得代码非常易于阅读。然而,主函数的执行分布在三个方法中,除了委托之外,这些方法之间没有直接引用。如果不考虑整体解决方案(包括所有外部组件),这可能会使执行流程难以理解。

                                                                                                                                                                                              The Loan­Broker class demon­strates the trade-off we made in our design. The code is free of ref­er­ences to mes­saging or thread-re­lated con­cepts (except for the in­her­it­ance from Asyn­cRe­questReplyS­er­vice), which makes the code very easy to read. How­ever, the ex­e­cu­tion of the main func­tion is spread across three meth­ods that make no direct ref­er­ence to each other be­sides the del­eg­ates. This can make the flow of ex­e­cu­tion hard to un­der­stand without con­sid­er­ing the total solu­tion, in­clud­ing all ex­ternal com­pon­ents.

                                                                                                                                                                                              重构贷款经纪人

                                                                                                                                                                                              Re­fact­or­ing the Loan Broker

                                                                                                                                                                                              当我们观察贷款经纪人的运作方式时,我们意识到我们正在分离数据和功能。我们有一个LoanBroker类的实例,它通过 ACT 集合模拟多个实例。虽然 ACT 非常有用,但它们似乎违背了面向对象编程的精神,因为它们将构成对象的数据和功能这两个部分分开。但是,如果我们以更好的方式使用委托,我们可以重构LoanBroker类以避免重复查找 ACT。委托本质上是类型安全的函数指针。因此,它们指向特定的对象实例。因此,不要向信用局和银行网关提供对唯一LoanBroker中方法的引用例如,我们可以使用委托来指向“流程对象”的特定实例,该实例维护当前状态,就像 ACT 所做的那样,但也包含贷款经纪人流程的逻辑。为此,我们将 ACT 转换为一个名为LoanBrokerProcess 的新类,并将消息处理函数移至该类中:

                                                                                                                                                                                              When we look at the way the loan broker func­tions, we real­ize that we are sep­ar­at­ing data and func­tion­al­ity. We have one in­stance of the Loan­Broker class that emu­lates mul­tiple in­stances by means of the ACT col­lec­tion. While ACTs are very useful, they seem to go against the spirit of object-ori­ented pro­gram­ming by sep­ar­at­ing data and func­tion­al­itythe two parts that make up an object. How­ever, we can re­factor the Loan­Broker class to avoid the re­peated lookup of the ACT if we use the del­eg­ates in a better way. Del­eg­ates are es­sen­tially type-safe func­tion point­ers. As such, they point to a spe­cific object in­stance. So, rather than sup­ply­ing the credit bureau and bank gate­way with a ref­er­ence to a method in the sole Loan­Broker in­stance, we can use the del­eg­ates to point to a spe­cific in­stance of a "pro­cess object" that main­tains the cur­rent state, as an ACT does, but also con­tains the logic of the loan broker pro­cess. To do this, we turn the ACT into a new class called Loan­Broker­Pro­cess and move the mes­sage hand­ler func­tions into this class:

                                                                                                                                                                                              内部类 LoanBrokerProcess
                                                                                                                                                                                              {
                                                                                                                                                                                                  受保护的 LoanBrokerPM 经纪人;
                                                                                                                                                                                                  受保护的字符串进程ID;
                                                                                                                                                                                                  受保护的 LoanQuoteRequest 贷款请求;
                                                                                                                                                                                                  受保护的Message消息;
                                                                                                                                                                                              
                                                                                                                                                                                                  受保护的 CreditBureauGateway 信用局网关;
                                                                                                                                                                                                  受保护的银行网关银行接口;
                                                                                                                                                                                              
                                                                                                                                                                                                  公共LoanBrokerProcess(LoanBrokerPM经纪人,字符串processID,
                                                                                                                                                                                                                           CreditBureauGateway 信用局网关,
                                                                                                                                                                                                                           银行网关银行网关,
                                                                                                                                                                                                                           LoanQuoteRequest 贷款请求、消息 msg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      this.broker = 经纪人;
                                                                                                                                                                                                      this.creditBureauGateway = 经纪人.CreditBureauGateway;
                                                                                                                                                                                                      this.bankInterface = 经纪人.BankInterface;
                                                                                                                                                                                                      this.processID = processID;
                                                                                                                                                                                                      this.loanRequest = 贷款请求;
                                                                                                                                                                                                      这个.message = msg;
                                                                                                                                                                                              
                                                                                                                                                                                                      CreditBureauRequest 信用请求 =
                                                                                                                                                                                                          LoanBrokerTranslator.GetCreditBureaurequest(loanRequest);
                                                                                                                                                                                                      CreditBureauGateway.GetCreditScore(creditRequest,
                                                                                                                                                                                                          新的 OnCreditReplyEvent(OnCreditReply), null);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  private void OnCreditReply(CreditBureauReply CreditReply, Object act)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      Console.WriteLine("收到的信用评分 -- SSN {0} 评分 {1} 长度 {2}",
                                                                                                                                                                                                          CreditReply.SSN、creditReply.CreditScore、creditReply.HistoryLength);
                                                                                                                                                                                                      银行报价请求 银行请求 =
                                                                                                                                                                                                          LoanBrokerTranslator.GetBankQuoteRequest(loanRequest, CreditReply);
                                                                                                                                                                                                      BankInterface.GetBestQuote(bankRequest, new OnBestQuoteEvent(OnBestQuote), null);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  private void OnBestQuote(BankQuoteReply bestQuote, Object act)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      LoanQuoteReply quoteReply = LoanBrokerTranslator.GetLoanQuoteReply
                                                                                                                                                                                                                                  (贷款请求,最佳报价);
                                                                                                                                                                                                      Console.WriteLine("最佳报价{0} {1}",
                                                                                                                                                                                                                        quoteReply.InterestRate, quoteReply.QuoteID);
                                                                                                                                                                                                      Broker.SendReply(quoteReply, 消息);
                                                                                                                                                                                                      Broker.OnProcessComplete(processID);
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              in­ternal class Loan­Broker­Pro­cess
                                                                                                                                                                                              {
                                                                                                                                                                                                  pro­tec­ted Loan­BrokerPM broker;
                                                                                                                                                                                                  pro­tec­ted String pro­cessID;
                                                                                                                                                                                                  pro­tec­ted Loan­Quote­Re­quest loan­Re­quest;
                                                                                                                                                                                                  pro­tec­ted Mes­sage mes­sage;
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted Cred­it­Bur­eau­G­ate­way cred­it­Bur­eau­G­ate­way;
                                                                                                                                                                                                  pro­tec­ted BankG­ate­way bank­In­ter­face;
                                                                                                                                                                                              
                                                                                                                                                                                                  public Loan­Broker­Pro­cess(Loan­BrokerPM broker, String pro­cessID,
                                                                                                                                                                                                                           Cred­it­Bur­eau­G­ate­way cred­it­Bur­eau­G­ate­way,
                                                                                                                                                                                                                           BankG­ate­way bankG­ate­way,
                                                                                                                                                                                                                           Loan­Quote­Re­quest loan­Re­quest, Mes­sage msg)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      this.broker = broker;
                                                                                                                                                                                                      this.cred­it­Bur­eau­G­ate­way = broker.Cred­it­Bur­eau­G­ate­way;
                                                                                                                                                                                                      this.bank­In­ter­face = broker.Bank­In­ter­face;
                                                                                                                                                                                                      this.pro­cessID = pro­cessID;
                                                                                                                                                                                                      this.loan­Re­quest = loan­Re­quest;
                                                                                                                                                                                                      this.mes­sage = msg;
                                                                                                                                                                                              
                                                                                                                                                                                                      Cred­it­Bur­eauRe­quest creditRe­quest =
                                                                                                                                                                                                          Loan­Broker­Trans­lator.GetCred­it­Bur­eaure­quest(loan­Re­quest);
                                                                                                                                                                                                      cred­it­Bur­eau­G­ate­way.GetCred­itScore(creditRe­quest,
                                                                                                                                                                                                          new On­CreditReplyEvent(On­CreditReply), null);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  private void On­CreditReply(Cred­it­Bur­eauReply creditReply, Object act)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      Con­sole.WriteLine("Re­ceived Credit Score -- SSN {0} Score {1} Length {2}",
                                                                                                                                                                                                          creditReply.SSN, creditReply.Cred­itScore, creditReply.His­toryLength);
                                                                                                                                                                                                      BankQuote­Re­quest bankRe­quest =
                                                                                                                                                                                                          Loan­Broker­Trans­lator.Get­BankQuote­Re­quest(loan­Re­quest, creditReply);
                                                                                                                                                                                                      bank­In­ter­face.Get­Be­stQuote(bankRe­quest, new On­Be­stQuoteEvent(On­Be­stQuote), null);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  private void On­Be­stQuote(BankQuoteReply be­stQuote, Object act)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      Loan­QuoteReply quoteReply = Loan­Broker­Trans­lator.GetLoan­QuoteReply
                                                                                                                                                                                                                                  (loan­Re­quest, be­stQuote);
                                                                                                                                                                                                      Con­sole.WriteLine("Best quote {0} {1}",
                                                                                                                                                                                                                        quoteReply.In­teres­tRate, quoteReply.QuoteID);
                                                                                                                                                                                                      broker.SendReply(quoteReply, mes­sage);
                                                                                                                                                                                                      broker.On­Pro­cessCom­plete(pro­cessID);
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              这些方法不再引用信用局网关和银行网关提供的 ACT 参数,因为所有必要的信息都存储在 LoanBrokerProcess 的实例中。 该过程完成后,它会使用LoanBrokerPM 从 AsyncRequestReplyService继承的SendReply方法发送回复消息。 接下来,它通知LoanBrokerPM该过程已完成。我们可以使用委托来实现此通知,但我们决定使用对代理的引用。

                                                                                                                                                                                              The meth­ods no longer ref­er­ence the ACT para­meter provided by the credit bureau gate­way and the bank gate­way be­cause all ne­ces­sary in­form­a­tion is stored in the in­stance of the Loan­Broker­Pro­cess. Once the pro­cess com­pletes, it sends the reply mes­sage using the SendReply method that the Loan­BrokerPM in­her­its from the Asyn­cRe­questReplyS­er­vice. Next, it no­ti­fies the Loan­BrokerPM of the com­ple­tion of the pro­cess. We could have im­ple­men­ted this no­ti­fic­a­tion using a del­eg­ate, but we de­cided to use a ref­er­ence to the broker in­stead.

                                                                                                                                                                                              使用LoanBrokerProcess 类简化了主要的贷款经纪人类:

                                                                                                                                                                                              Using the Loan­Broker­Pro­cess class sim­pli­fies the main loan broker class:

                                                                                                                                                                                              内部类 LoanBrokerPM :AsyncRequestReplyService
                                                                                                                                                                                              {
                                                                                                                                                                                                  受保护的 CreditBureauGateway 信用局网关;
                                                                                                                                                                                                  受保护的银行网关银行接口;
                                                                                                                                                                                                  受保护的 IDictionary activeProcesses = (IDictionary)(new Hashtable());
                                                                                                                                                                                              
                                                                                                                                                                                                  公共LoanBrokerPM(字符串请求队列名称,
                                                                                                                                                                                                                      字符串creditRequestQueueName,字符串creditReplyQueueName,
                                                                                                                                                                                                                      字符串银行回复队列名称,
                                                                                                                                                                                                                      BankConnectionManager 连接管理器):base(requestQueueName)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      CreditBureauGateway = new CreditBureauGateway(creditRequestQueueName,
                                                                                                                                                                                                                                                    信用回复队列名称);
                                                                                                                                                                                                      CreditBureauGateway.Listen();
                                                                                                                                                                                              
                                                                                                                                                                                                      BankInterface = new BankGateway(bankReplyQueueName, 连接管理器);
                                                                                                                                                                                                      银行接口.Listen();
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  受保护的覆盖类型 GetRequestBodyType()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      返回类型(LoanQuoteRequest);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  protected override void ProcessMessage(Object o, Message 消息)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      贷款报价请求报价请求;
                                                                                                                                                                                                      quoteRequest = (LoanQuoteRequest)o;
                                                                                                                                                                                              
                                                                                                                                                                                                      String processID = 消息.Id;
                                                                                                                                                                                                      LoanBrokerProcess 新流程 =
                                                                                                                                                                                                          新的LoanBrokerProcess(这个,processID,creditBureauGateway,
                                                                                                                                                                                                                                银行接口、报价请求、消息);
                                                                                                                                                                                                      activeProcesses.Add(processID, newProcess);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  公共无效OnProcessComplete(字符串进程ID)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      activeProcesses.Remove(processID);
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              in­ternal class Loan­BrokerPM : Asyn­cRe­questReplyS­er­vice
                                                                                                                                                                                              {
                                                                                                                                                                                                  pro­tec­ted Cred­it­Bur­eau­G­ate­way cred­it­Bur­eau­G­ate­way;
                                                                                                                                                                                                  pro­tec­ted BankG­ate­way bank­In­ter­face;
                                                                                                                                                                                                  pro­tec­ted IDic­tion­ary act­ive­Pro­cesses = (IDic­tion­ary)(new Hasht­able());
                                                                                                                                                                                              
                                                                                                                                                                                                  public Loan­BrokerPM(String re­questQueueName,
                                                                                                                                                                                                                      String creditRe­questQueueName, String creditReplyQueueName,
                                                                                                                                                                                                                      String bankReplyQueueName,
                                                                                                                                                                                                                      Bank­Con­nec­tion­Man­ager con­nec­tion­Man­ager): base(re­questQueueName)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      cred­it­Bur­eau­G­ate­way = new Cred­it­Bur­eau­G­ate­way(creditRe­questQueueName,
                                                                                                                                                                                                                                                    creditReplyQueueName);
                                                                                                                                                                                                      cred­it­Bur­eau­G­ate­way.Listen();
                                                                                                                                                                                              
                                                                                                                                                                                                      bank­In­ter­face = new BankG­ate­way(bankReplyQueueName, con­nec­tion­Man­ager);
                                                                                                                                                                                                      bank­In­ter­face.Listen();
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted over­ride Type GetRe­quest­Bo­dy­Type()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      return typeof(Loan­Quote­Re­quest);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  pro­tec­ted over­ride void Pro­cess­Mes­sage(Object o, Mes­sage mes­sage)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      Loan­Quote­Re­quest quote­Re­quest;
                                                                                                                                                                                                      quote­Re­quest = (Loan­Quote­Re­quest)o;
                                                                                                                                                                                              
                                                                                                                                                                                                      String pro­cessID = mes­sage.Id;
                                                                                                                                                                                                      Loan­Broker­Pro­cess new­Pro­cess =
                                                                                                                                                                                                          new Loan­Broker­Pro­cess(this, pro­cessID, cred­it­Bur­eau­G­ate­way,
                                                                                                                                                                                                                                bank­In­ter­face, quote­Re­quest, mes­sage);
                                                                                                                                                                                                      act­ive­Pro­cesses.Add(pro­cessID, new­Pro­cess);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  public void On­Pro­cessCom­plete(String pro­cessID)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      act­ive­Pro­cesses.Remove(pro­cessID);
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              这个LoanBrokerPM基本上是流程。当新消息到达时,它会创建一个新的流程实例。当流程完成时,流程管理器从活动流程列表中删除该流程实例。流程管理器使用消息ID作为分配给每个流程实例的唯一流程ID。现在,我们只需编辑LoanBrokerProcess 类即可更改贷款经纪人的行为,该类除了传递消息对象外,没有对消息传递的引用。看起来关注适当的封装和重构得到了回报。下面的类图总结了贷款经纪人的内部结构:

                                                                                                                                                                                              This Loan­BrokerPM is ba­sic­ally a gen­eric im­ple­ment­a­tion of a Pro­cess Man­ager. It cre­ates a new pro­cess in­stance when a new mes­sage ar­rives. When a pro­cess com­pletes, the pro­cess man­ager re­moves the pro­cess in­stance from the list of active pro­cesses. The pro­cess man­ager uses the mes­sage ID as the unique pro­cess ID as­signed to each pro­cess in­stance. We can now change the be­ha­vior of the loan broker just by edit­ing the Loan­Broker­Pro­cess class, which has no ref­er­ences to mes­saging be­sides passing the mes­sage object around. It looks like paying at­ten­tion to proper en­cap­su­la­tion and re­fact­or­ing paid off. The fol­low­ing class dia­gram sum­mar­izes the in­ternal struc­ture of the loan broker:

                                                                                                                                                                                              贷款经纪人类图

                                                                                                                                                                                              Loan Broker Class Dia­gram

                                                                                                                                                                                              图形/09inf21.gif

                                                                                                                                                                                              把它们放在一起

                                                                                                                                                                                              Put­ting It All To­gether

                                                                                                                                                                                              唯一剩下的部分是测试客户端。测试客户端的设计与信用局网关的设计类似。测试客户端可以发出指定数量的重复请求,并将传入响应与未完成的请求相关联。一旦我们启动所有流程(银行、信用局和贷款经纪人),我们就可以执行该示例。我们使用许多简单的主类来启动相应的组件作为控制台应用程序。我们在屏幕上看到一系列活动,表明系统中的消息流(见图)。

                                                                                                                                                                                              The only re­main­ing piece is the test client. The test client design is sim­ilar to that of the credit bureau gate­way. The test client can make a spe­cified number of re­peated re­quests and cor­rel­ate in­com­ing re­sponses to out­stand­ing re­quests. Once we start all pro­cesses (banks, credit bureau, and the loan broker), we can ex­ecute the ex­ample. We use a number of simple main classes to start the re­spect­ive com­pon­ents as con­sole ap­plic­a­tions. We see a flurry of activ­ity on the screen, in­dic­at­ing the flow of mes­sages through the system (see figure).

                                                                                                                                                                                              运行 MSMQ 示例

                                                                                                                                                                                              Run­ning the MSMQ Ex­ample

                                                                                                                                                                                              图形/09inf22.gif

                                                                                                                                                                                              提高绩效

                                                                                                                                                                                              Im­prov­ing Per­form­ance

                                                                                                                                                                                              现在我们已经运行了完整的解决方案,我们可以收集一些性能指标来比较异步解决方案与同步解决方案的吞吐量。使用测试数据生成器,我们向贷款经纪人发送 50 个随机生成的请求。测试数据生成器报告,接收 50 条回复消息花了 33 秒。

                                                                                                                                                                                              Now that we have the com­plete solu­tion run­ning, we can gather some per­form­ance met­rics to com­pare the through­put of the asyn­chron­ous solu­tion to the syn­chron­ous solu­tion. Using the test data gen­er­ator, we send 50 ran­domly gen­er­ated re­quests to the loan broker. The test data gen­er­ator re­ports that it took 33 seconds to re­ceive the 50 reply mes­sages.

                                                                                                                                                                                              发送 50 个报价请求

                                                                                                                                                                                              Send­ing 50 Quote Re­quests

                                                                                                                                                                                              图形/09inf23.gif

                                                                                                                                                                                              人们很容易认为每个请求花费了 33/50 = 0.6 秒。错误的!贷款经纪人的吞吐量为33 秒内 50 个请求,但某些请求需要 27 秒才能完成。为什么系统这么慢?我们来看一下测试运行过程中消息队列的快照:

                                                                                                                                                                                              It would be tempt­ing to think that each re­quest took 33/50 = 0.6 seconds. Wrong! The through­put of the loan broker is 50 re­quests in 33 seconds, but some of the re­quests took 27 seconds to com­plete. Why is the system so slow? Let's look at a snap­shot of the mes­sage queue during the test run:

                                                                                                                                                                                              31 条消息在信用请求队列中排队

                                                                                                                                                                                              31 Mes­sages Are Queued Up in the Credit Re­quest Queue

                                                                                                                                                                                              图形/09inf24.jpg

                                                                                                                                                                                              三十一条消息在信用局请求队列中排队!显然,信用局是我们的瓶颈,因为所有报价请求都必须首先经过信用局。现在我们可以获得松散耦合的一些回报,并启动信用局的两个额外实例。现在,我们正在运行信用局服务的三个并行实例。这应该可以解决我们的瓶颈,对吧?让我们来看看:

                                                                                                                                                                                              Thirty-one mes­sages are queued up in the credit bureau re­quest queue! Ap­par­ently, the credit bureau is our bot­tle­neck, be­cause all quote re­quests have to go through the credit bureau first. Now we can reap some of the re­wards of loose coup­ling and start two ad­di­tional in­stances of the credit bureau. We now have three par­al­lel in­stances of the credit bureau ser­vice run­ning. This should fix our bot­tle­neck, right? Let's see:

                                                                                                                                                                                              使用三个信用局实例发送 50 个报价请求

                                                                                                                                                                                              Send­ing 50 Quote Re­quests, Using Three Credit Bureau In­stances

                                                                                                                                                                                              图形/09inf25.gif

                                                                                                                                                                                              处理所有 50 条消息的总时间减少到 21 秒,其中最长的请求等待响应时间为 16 秒。平均而言,客户等待贷款请求回复的时间为 8.63 秒,是原始版本的一半。看起来我们消除了瓶颈,但消息吞吐量并没有像我们希望的那样显着增加。但请记住,对于这个简单的示例,我们在单个 CPU 上运行所有进程,以便所有进程竞争相同的资源。让我们看一下新的队列统计数据,以验证信用局的瓶颈实际上已得到纠正:

                                                                                                                                                                                              The total time to pro­cess all 50 mes­sages is re­duced to 21 seconds, with the longest re­quest wait­ing for a re­sponse for 16 seconds. On av­er­age, the client had to wait for a reply to the loan re­quest for 8.63 seconds, half of the ori­ginal ver­sion. It looks like we elim­in­ated the bot­tle­neck, but the mes­sage through­put did not in­crease as dra­mat­ic­ally as we might have hoped. Re­mem­ber, though, that for this simple ex­ample we are run­ning all pro­cesses on a single CPU so that all pro­cesses com­pete for the same re­sources. Let's look at the new queue stat­ist­ics to verify that the credit bureau bot­tle­neck is in fact cor­rec­ted:

                                                                                                                                                                                              现在 Bank 5 似乎成为瓶颈

                                                                                                                                                                                              Now Bank 5 Ap­pears to Be a Bot­tle­neck

                                                                                                                                                                                              图形/09inf26.gif

                                                                                                                                                                                              看起来我们消除了一个瓶颈只是为了找到一个新的 Bank 5。为什么是 Bank 5?5号银行是一家当铺;它向所有人提供贷款,因此几乎每个报价请求都包含 Bank 5。我们现在可以继续启动 Bank 5 的多个实例,但期望当铺仅仅为了提高吞吐量而运行多个实例是不现实的。我们的另一个选择是更改银行请求的路由逻辑。由于当铺收取的溢价比其他银行高,因此只有在没有其他银行提供报价的情况下,当铺的报价往往是最低报价。考虑到这一观察结果,如果报价也可以由另一家银行提供服务,我们可以通过不将请求路由到当铺来提高系统效率。

                                                                                                                                                                                              It looks like we elim­in­ated one bot­tle­neck just to find a new one­Bank 5. Why Bank 5? Bank 5 is a pawn shop; it offers loans to every­body, so Bank 5 is part of almost every quote re­quest. We could now go on to start mul­tiple in­stances of Bank 5, but it's not real­istic to expect the pawn shop to run mul­tiple in­stances just to im­prove our through­put. Our other option is to change the rout­ing logic for the bank re­quests. Since the pawn shop charges a sub­stan­tial premium over the other banks, its quote tends to be the lowest quote only in those cases where no other bank provided a quote. Taking this ob­ser­va­tion into ac­count, we can im­prove the ef­fi­ciency of the system by not rout­ing re­quests to the pawn shop if the quote can also be ser­viced by an­other bank. This change will not affect the over­all be­ha­vior of the system.

                                                                                                                                                                                              我们仅针对无法由任何其他银行提供服务的报价请求更改BankConnectionManager 以包含银行 5。修改后的BankConnectionManager 如下所示

                                                                                                                                                                                              We change the Bank­Con­nec­tion­Man­ager to in­clude Bank 5 only for those quote re­quests that cannot be ser­viced by any other bank. The mod­i­fied Bank­Con­nec­tion­Man­ager looks like this:

                                                                                                                                                                                              
                                                                                                                                                                                              内部类 BankConnectionManager
                                                                                                                                                                                              {
                                                                                                                                                                                                  静态受保护的 BankConnection[] 银行 = {new Bank1(), new Bank2(), new Bank3(), new
                                                                                                                                                                                              图形/ccc.gif银行4() };
                                                                                                                                                                                                  静态受保护的 BankConnection catchAll = new Bank5();
                                                                                                                                                                                              
                                                                                                                                                                                                  公共 IMessageSender[] GetEligibleBankQueues(int CreditScore, int HistoryLength,
                                                                                                                                                                                                                                                int 贷款金额)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      ArrayList 贷方 = new ArrayList();
                                                                                                                                                                                              
                                                                                                                                                                                                      for (int 索引 = 0; 索引 < 银行.长度; 索引++)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          if (银行[索引].CanHandleLoanRequest(CreditScore, HistoryLength,
                                                                                                                                                                                                                                                贷款额度))
                                                                                                                                                                                                              贷方.Add(银行[index].Queue);
                                                                                                                                                                                                      }
                                                                                                                                                                                                      if (lenders.Count == 0)
                                                                                                                                                                                                          贷方.Add(catchAll.Queue);
                                                                                                                                                                                                      IMessageSender[] lenderArray = (IMessageSender [])Array.CreateInstance
                                                                                                                                                                                                                                     (typeof(IMessageSender), 贷方.Count);
                                                                                                                                                                                                      贷方.CopyTo(lenderArray);
                                                                                                                                                                                                      返回贷方数组;
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              
                                                                                                                                                                                              in­ternal class Bank­Con­nec­tion­Man­ager
                                                                                                                                                                                              {
                                                                                                                                                                                                  static pro­tec­ted Bank­Con­nec­tion[] banks = {new Bank1(), new Bank2(), new Bank3(), new
                                                                                                                                                                                               Bank4() };
                                                                                                                                                                                                  static pro­tec­ted Bank­Con­nec­tion catchAll = new Bank5();
                                                                                                                                                                                              
                                                                                                                                                                                                  public IMes­sageSender[] GetEli­gible­BankQueues(int Cred­itScore, int His­toryLength,
                                                                                                                                                                                                                                                int LoanAmount)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      Ar­rayL­ist lenders = new Ar­rayL­ist();
                                                                                                                                                                                              
                                                                                                                                                                                                      for (int index = 0; index < banks.Length; index++)
                                                                                                                                                                                                      {
                                                                                                                                                                                                          if (banks[index].Can­Handle­L­oan­Re­quest(Cred­itScore, His­toryLength,
                                                                                                                                                                                                                                                LoanAmount))
                                                                                                                                                                                                              lenders.Add(banks[index].Queue);
                                                                                                                                                                                                      }
                                                                                                                                                                                                      if (lenders.Count == 0)
                                                                                                                                                                                                          lenders.Add(catchAll.Queue);
                                                                                                                                                                                                      IMes­sageSender[] lenderAr­ray = (IMes­sageSender [])Array.Cre­ateIn­stance
                                                                                                                                                                                                                                     (typeof(IMes­sageSender), lenders.Count);
                                                                                                                                                                                                      lenders.CopyTo(lenderAr­ray);
                                                                                                                                                                                                      return lenderAr­ray;
                                                                                                                                                                                                  }
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              使用修改后的代码运行会产生下图所示的结果。

                                                                                                                                                                                              Run­ning with the mod­i­fied code pro­duces the res­ults shown in the fol­low­ing figure.

                                                                                                                                                                                              使用三个信用局实例和修改后的 BankConnectionManager 发送 50 个报价请求

                                                                                                                                                                                              Send­ing 50 Quote Re­quests Using Three Credit Bureau In­stances and a Mod­i­fied Bank­Con­nec­tion­Man­ager

                                                                                                                                                                                              图形/09inf27.gif

                                                                                                                                                                                              现在的测试结果显示,所有 50 个请求均在 12 秒内得到满足,是原始时间的一半。更重要的是,贷款报价请求的平均处理时间现在不到 4 秒,比初始版本提高了四倍。此示例演示了使用收件人列表进行预测路由的优势。由于贷款经纪人可以控制路由,因此我们可以决定在路由逻辑中构建多少“智能”,而不需要对外部各方进行任何更改。代价是贷款经纪人变得越来越依赖于对内部各方的了解。例如,原来的BankConnectionManager由于对待所有银行一视同仁,修改后的版本依赖于这样一个事实:银行 5 是一个包罗万象的提供商,只有在没有其他选择的情况下才应联系它。如果银行 5 开始提供更好的利率,客户可能不再获得最好的交易。

                                                                                                                                                                                              The test res­ults now show that all 50 re­quests were ser­viced in 12 seconds, half of the ori­ginal time. More im­port­antly, the av­er­age time to ser­vice a loan quote re­quest is now under 4 seconds, a fourfold im­prove­ment over the ini­tial ver­sion. This ex­ample demon­strates the ad­vant­age of pre­dict­ive rout­ing by using a Re­cip­i­ent List. Be­cause the loan broker has con­trol over the rout­ing, we can decide how much "in­tel­li­gence" we can build into the rout­ing logic without re­quir­ing any changes to the ex­ternal parties. The trade-off is that the loan broker be­comes more and more de­pend­ent on know­ledge about the in­ternal parties. For ex­ample, while the ori­ginal Bank­Con­nec­tion­Man­ager treated all banks as equal, the mod­i­fied ver­sion relies on the fact that Bank 5 is a catch-all pro­vider that should be con­tac­ted only if there are no other op­tions. If Bank 5 starts to offer better rates, the cli­ents may no longer get the best pos­sible deal.

                                                                                                                                                                                              该屏幕剪辑还表明,响应消息不一定按照发出请求的顺序到达。我们可以看到测试客户端在收到请求 43 的响应之后立即收到了对请求号 48 的响应。因为我们没有丢失任何响应,这意味着测试客户端在收到响应 43 之前收到了响应 44 到 47。请求传递编号 43?请求 43 似乎已路由至 General Retail Bank(银行 3)。继当铺之后,这家银行的选择标准限制第二少,并且比其他银行更有可能收到请求。如果请求 44 到 47 不符合一般零售银行的标准,则银行网关将收到这些请求的所有响应,银行3队列。因为我们的贷款经纪人是真正的事件驱动型,所以它会在收到所有银行报价后立即回复贷款请求。因此,如果请求 44 的银行报价先于请求 43 的银行报价到达,则贷款经纪人将首先发送请求 44 的回复消息。此场景还强调了消息中相关标识符的重要性,以便测试客户端可以将响应与请求匹配,即使它们到达时顺序不正确。

                                                                                                                                                                                              The screen clip also demon­strates that re­sponse mes­sages do not ne­ces­sar­ily arrive in the order in which the re­quests were made. We can see that the test client re­ceived the re­sponse to re­quest number 48 right after the re­sponse to re­quest 43. Be­cause we are not miss­ing any re­sponses, this means that the test client re­ceived re­sponses 44 through 47 before it re­ceived re­sponse 43. How did these re­quests pass number 43? It looks like re­quest 43 was routed to the Gen­eral Retail Bank (Bank 3). After the Pawn Shop, this bank has the next least re­strict­ive se­lec­tion cri­teria and is more likely than the other banks to be backed up with re­quests. If re­quests 44 through 47 did not match the Gen­eral Retail Bank's cri­teria, the bank gate­way would have re­ceived all re­sponses for these re­quests, while the quote re­quest for re­quest 43 was still sit­ting in the bank3Queue. Be­cause our loan broker is truly event-driven, it will reply to a loan re­quest as soon as it re­ceives all bank quotes. As a result, if the bank quotes for re­quest 44 arrive before the bank quotes for number 43, the loan broker will send the reply mes­sage for re­quest 44 first. This scen­ario also high­lights the im­port­ance of the Cor­rel­a­tion Iden­ti­fier in the mes­sages so that the test client can match re­sponses to re­quests even if they arrive out of order.

                                                                                                                                                                                              调整异步、基于消息的系统可能是一项非常复杂的任务。我们的示例展示了一些识别和解决瓶颈的最基本技术。但即使是我们简单的例子也清楚地表明,纠正一个问题(征信局瓶颈)可能会导致另一个问题(Bank 5 瓶颈)浮出水面。我们还可以清楚地看到异步消息传递和事件驱动消费者的优势。我们能够在 12 秒内处理 50 个报价请求,而同步解决方案则需要 8 或 10 倍的时间!

                                                                                                                                                                                              Tuning asyn­chron­ous, mes­sage-based sys­tems can be a very com­plex task. Our ex­ample shows some of the most basic tech­niques of identi­fy­ing and resolv­ing bot­tle­necks. But even our simple ex­ample made it clear that cor­rect­ing one prob­lem (the credit bureau bot­tle­neck) can cause an­other prob­lem (the Bank 5 bot­tle­neck) to sur­face. We can also clearly see the ad­vant­ages of asyn­chron­ous mes­saging and event-driven con­sumers. We were able to pro­cess 50 quote re­quests in 12 secondsa syn­chron­ous solu­tion would have taken 8 or 10 times as long!

                                                                                                                                                                                              关于测试的几句话

                                                                                                                                                                                              A Few Words on Test­ing

                                                                                                                                                                                              贷款经纪人示例演示了一个简单的应用程序在变得分布式、异步和事件驱动后如何变得相当复杂。我们现在有十几个类,并始终使用委托来处理异步消息处理的事件驱动性质。复杂性的增加也意味着缺陷风险的增加。异步特性使得这些缺陷难以重现或排除故障,因为它们取决于特定的时间条件。由于这些额外的风险,消息传递解决方案需要非常彻底的测试方法。我们可以写一本关于测试消息传递解决方案的整本书,但现在我想包括一些简单的、可操作的测试建议,总结为以下三个规则:

                                                                                                                                                                                              The loan broker ex­ample demon­strates how a simple ap­plic­a­tion can become reas­on­ably com­plex once it be­comes dis­trib­uted, asyn­chron­ous, and event-driven. We now have a dozen classes and use del­eg­ates through­out to deal with the event-driven nature of asyn­chron­ous mes­sage pro­cess­ing. The in­creased com­plex­ity also means in­creased risk of de­fects. The asyn­chron­ous nature makes these de­fects hard to re­pro­duce or troubleshoot be­cause they depend on spe­cific tem­poral con­di­tions. Be­cause of these ad­di­tional risks, mes­saging solu­tions re­quire a very thor­ough ap­proach to test­ing. We could write a whole book on test­ing mes­saging solu­tions, but for now I want to in­clude some simple, ac­tion­able advice on test­ing, sum­mar­ized in the fol­low­ing three rules:

                                                                                                                                                                                              • 通过使用接口和实现类将应用程序与消息传递实现隔离。

                                                                                                                                                                                              • Isol­ate the ap­plic­a­tion from the mes­saging im­ple­ment­a­tion by using in­ter­faces and im­ple­ment­a­tion classes.

                                                                                                                                                                                              • 在将业务逻辑插入消息传递环境之前,使用单元测试用例测试业务逻辑。

                                                                                                                                                                                              • Test the busi­ness logic with unit test cases before plug­ging it into the mes­saging en­vir­on­ment.

                                                                                                                                                                                              • 提供消息传递层的模拟实现,允许您同步测试。

                                                                                                                                                                                              • Provide a mock im­ple­ment­a­tion of the mes­saging layer that allows you to test syn­chron­ously.

                                                                                                                                                                                              将应用程序与消息传递实现隔离

                                                                                                                                                                                              测试单个应用程序比测试通过消息通道连接的多个分布式应用程序要容易得多。单个应用程序允许我们跟踪完整的执行路径,我们不需要复杂的启动过程来启动所有组件,并且不需要在测试之间清除通道(请参阅Channel Purger [xxx])。有时,在测试其他函数时删除一些外部函数很有用。例如,当我们测试银行网关时,我们可能会删除信用局网关,而不是实际将消息发送到外部信用局进程。

                                                                                                                                                                                              Test­ing a single ap­plic­a­tion is much easier than test­ing mul­tiple, dis­trib­uted ap­plic­a­tions con­nec­ted by mes­saging chan­nels. A single ap­plic­a­tion allows us to trace through the com­plete ex­e­cu­tion path, we do not need a com­plex star­tup pro­ced­ure to fire up all com­pon­ents, and there is no need to purge chan­nels between tests (see Chan­nel Purger [xxx]). Some­times it is useful to stub out some ex­ternal func­tions while test­ing others. For ex­ample, while we are test­ing the bank gate­way, we might as well stub out the credit bureau gate­way in­stead of ac­tu­ally send­ing mes­sages to an ex­ternal credit bureau pro­cess.

                                                                                                                                                                                              我们如何才能实现在单个应用程序内进行测试的一些好处,同时对应用程序代码的影响最小?我们可以将消息网关的实现与接口定义分开。这使我们能够提供该接口的多种实现。

                                                                                                                                                                                              How can we achieve some of the be­ne­fits of test­ing inside a single ap­plic­a­tion with a min­imal impact on the ap­plic­a­tion code? We can sep­ar­ate the im­ple­ment­a­tion of a Mes­saging Gate­way from the in­ter­face defin­i­tion. That allows us to provide mul­tiple im­ple­ment­a­tions of the in­ter­face.

                                                                                                                                                                                              将征信局接口与实现分离

                                                                                                                                                                                              Sep­ar­at­ing Credit Bureau In­ter­face from Im­ple­ment­a­tion

                                                                                                                                                                                              图形/09inf28.gif

                                                                                                                                                                                              因为我们将所有消息传递特定逻辑封装在信用局网关内,所以我们可以定义一个非常简单的接口:

                                                                                                                                                                                              Be­cause we en­cap­su­lated all mes­saging-spe­cific logic inside the credit bureau gate­way, we can define a very simple in­ter­face:

                                                                                                                                                                                              公共接口 ICreditBureauGateway
                                                                                                                                                                                              {
                                                                                                                                                                                                  void GetCreditScore(CreditBureauRequest quoteRequest,
                                                                                                                                                                                                                      OnCreditReplyEvent OnCreditResponse, 对象 ACT);
                                                                                                                                                                                                  无效监听();
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              public in­ter­face ICred­it­Bur­eau­G­ate­way
                                                                                                                                                                                              {
                                                                                                                                                                                                  void GetCred­itScore(Cred­it­Bur­eauRe­quest quote­Re­quest,
                                                                                                                                                                                                                      On­CreditReplyEvent On­CreditRe­sponse, Object ACT);
                                                                                                                                                                                                  void Listen();
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              例如,我们可以创建一个模拟信用局网关实现,它实际上并不连接到任何消息队列,而是直接调用GetCreditScore方法内的指定委托。此模拟实现包含与实际信用局相同的逻辑,因此贷款经纪人的其余部分完全不知道此切换。

                                                                                                                                                                                              For ex­ample, we can create a mock credit bureau gate­way im­ple­ment­a­tion that does not ac­tu­ally con­nect to any mes­sage queue but rather in­vokes the spe­cified del­eg­ate right inside the GetCred­itScore method. This mock im­ple­ment­a­tion con­tains the same logic as the actual credit bureau, so the re­mainder of the loan broker is com­pletely un­aware of this switch­eroo.

                                                                                                                                                                                              公共类 MockCreditBureauGatewayImp : ICreditBureauGateway
                                                                                                                                                                                              {
                                                                                                                                                                                              
                                                                                                                                                                                                  私有随机随机 = new Random();
                                                                                                                                                                                              
                                                                                                                                                                                                  公共 MockCreditBureauGatewayImp()
                                                                                                                                                                                                  { }
                                                                                                                                                                                              
                                                                                                                                                                                                  公共无效GetCreditScore(CreditBureauRequest quoteRequest,
                                                                                                                                                                                                                             OnCreditReplyEvent OnCreditResponse,对象 ACT)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      CreditBureauReply 回复 = new CreditBureauReply();
                                                                                                                                                                                                      回复.CreditScore = (int)(随机.Next(600) + 300);
                                                                                                                                                                                                      回复.HistoryLength = (int)(随机.Next(19) + 1);
                                                                                                                                                                                                      回复.SSN = quoteRequest.SSN;
                                                                                                                                                                                                      OnCreditResponse(回复, ACT);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  公共无效监听()
                                                                                                                                                                                                  { }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              public class Mock­Cred­it­Bur­eau­G­ate­way­Imp : ICred­it­Bur­eau­G­ate­way
                                                                                                                                                                                              {
                                                                                                                                                                                              
                                                                                                                                                                                                  private Random random = new Random();
                                                                                                                                                                                              
                                                                                                                                                                                                  public Mock­Cred­it­Bur­eau­G­ate­way­Imp()
                                                                                                                                                                                                  { }
                                                                                                                                                                                              
                                                                                                                                                                                                  public void GetCred­itScore(Cred­it­Bur­eauRe­quest quote­Re­quest,
                                                                                                                                                                                                                             On­CreditReplyEvent On­CreditRe­sponse, Object ACT)
                                                                                                                                                                                                  {
                                                                                                                                                                                                      Cred­it­Bur­eauReply  reply = new Cred­it­Bur­eauReply();
                                                                                                                                                                                                      reply.Cred­itScore =  (int)(random.Next(600) + 300);
                                                                                                                                                                                                      reply.His­toryLength = (int)(random.Next(19) + 1);
                                                                                                                                                                                                      reply.SSN = quote­Re­quest.SSN;
                                                                                                                                                                                                      On­CreditRe­sponse(reply, ACT);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  public void Listen()
                                                                                                                                                                                                  { }
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              使用单元测试用例测试业务逻辑

                                                                                                                                                                                              CreditBureau 类的实现展示了消息传递相关函数(封装在基类中)和业务逻辑(在我们的简单示例中简化为随机生成器)的清晰分离。在现实场景中,业务逻辑(希望)会更加复杂。在这种情况下,将getCreditScoregetCreditHistoryLength方法一起移动到一个单独的类中是值得的,该类对消息传递层没有任何依赖关系(尽管不太明显,继承仍然携带从子类到基类和相关的依赖关系)类)。然后我们可以使用单元测试工具,例如nUnit(http://www.nunit.org)编写测试用例而不必担心消息传递。

                                                                                                                                                                                              The im­ple­ment­a­tion of the Cred­it­Bur­eau class demon­strated a clean sep­ar­a­tion of mes­saging-re­lated func­tions (en­cap­su­lated in the base class) and the busi­ness logic (re­duced to a ran­dom­izer in our simple ex­ample). In a real-life scen­ario, the busi­ness logic would (hope­fully) be some­what more com­plex. In that case, it pays off to move the getCred­itScore and getCreditH­is­toryLength meth­ods to­gether into a sep­ar­ate class that does not have any de­pend­ency on the mes­saging layer (even though it is less vis­ible, in­her­it­ance still car­ries de­pend­en­cies from the sub­class to the base class and re­lated classes). We can then use a unit test tool such as nUnit (http://www.nunit.org) to write test cases without having to worry about mes­saging.

                                                                                                                                                                                              提供消息传递层的模拟实现

                                                                                                                                                                                              ICreditBureauGateway的模拟实现简单而有效。但它也替换了所有与信用局网关相关的代码,因此必须单独测试类。如果我们想要消除对消息队列的依赖(以及相关的性能影响),但仍执行 CreditBureauGatewayImp 类内的代码,则可以使用IMessageReceiver和 IMessageSender接口的模拟实现。一个简单的模拟实现可能如下所示:

                                                                                                                                                                                              The mock im­ple­ment­a­tion of the ICred­it­Bur­eau­G­ate­way is simple and ef­fect­ive. But it also re­places all credit bureau gate­wayre­lated code so that the class Cred­it­Bur­eau­G­ate­way­Imp has to be tested sep­ar­ately. If we want to elim­in­ate the de­pend­ency (and the as­so­ci­ated per­form­ance hit) on mes­sage queues but still ex­ecute the code inside the Cred­it­Bur­eau­G­ate­way­Imp class, we can use a mock im­ple­ment­a­tion of the IMes­sageRe­ceiver and IMes­sageSender in­ter­faces. A simple mock im­ple­ment­a­tion could look like this:

                                                                                                                                                                                              公共类 MockQueue:IMessageSender、IMessageReceiver
                                                                                                                                                                                              {
                                                                                                                                                                                                  私有 OnMsgEvent onMsg = new OnMsgEvent(DoNothing);
                                                                                                                                                                                              
                                                                                                                                                                                                  公共无效发送(消息msg){
                                                                                                                                                                                                      onMsg(消息);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  私有静态无效DoNothing(消息msg){
                                                                                                                                                                                              
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  公共 OnMsgEvent OnMessage
                                                                                                                                                                                                  {
                                                                                                                                                                                                      获取{返回onMsg; }
                                                                                                                                                                                                      设置 { onMsg = 值; }
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  公共无效开始()
                                                                                                                                                                                                  {
                                                                                                                                                                                              
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  公共消息队列 GetQueue()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      返回空值;
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                              }
                                                                                                                                                                                              
                                                                                                                                                                                              public class Mock­Queue: IMes­sageSender, IMes­sageRe­ceiver
                                                                                                                                                                                              {
                                                                                                                                                                                                  private OnMs­gEvent onMsg = new OnMs­gEvent(DoNoth­ing);
                                                                                                                                                                                              
                                                                                                                                                                                                  public void Send(Mes­sage msg){
                                                                                                                                                                                                      onMsg(msg);
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  private static void DoNoth­ing(Mes­sage msg){
                                                                                                                                                                                              
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  public OnMs­gEvent On­Mes­sage
                                                                                                                                                                                                  {
                                                                                                                                                                                                      get { return onMsg; }
                                                                                                                                                                                                      set { onMsg = value; }
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  public void Begin()
                                                                                                                                                                                                  {
                                                                                                                                                                                              
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                                  public Mes­sageQueue GetQueue()
                                                                                                                                                                                                  {
                                                                                                                                                                                                      return null;
                                                                                                                                                                                                  }
                                                                                                                                                                                              
                                                                                                                                                                                              }
                                                                                                                                                                                              

                                                                                                                                                                                              我们可以看到,Send立即触发了onMsg委托,而没有使用任何消息队列。要将此模拟队列用于信用局网关,我们必须确保回复正确类型的消息。我们无法简单地将请求消息传递回回复消息。此实现未在此处显示,但如果例如我们使用预设回复消息,则可以很简单。

                                                                                                                                                                                              We can see that the Send trig­gers the onMsg del­eg­ate im­me­di­ately without using any mes­sage queue. To use this mock queue for the credit bureau gate­way, we would have to make sure to reply with a mes­sage of the cor­rect type. We would not be able to simply pass the re­quest mes­sage back to the reply mes­sage. This im­ple­ment­a­tion is not shown here but can be simple if, for ex­ample, we use a canned reply mes­sage.

                                                                                                                                                                                              本例的局限性

                                                                                                                                                                                              Lim­it­a­tions of This Ex­ample

                                                                                                                                                                                              本节提醒我们,由于组件之间的异步特性和松散耦合,即使是简单的消息传递系统(贷款经纪人实际上只需执行两个步骤:获取信用评分和获得最佳银行报价)也会变得相当复杂。然而,我们仍然采取了一些快捷方式来保持示例的易于管理。具体来说,该示例不涉及以下主题:

                                                                                                                                                                                              This sec­tion re­minded us that even a simple mes­saging system (the loan broker really has to ex­ecute only two steps: get the credit score and get the best bank quote) can get fairly com­plex due to the asyn­chron­ous nature and loose coup­ling between com­pon­ents. How­ever, we still took a number of short­cuts to keep the ex­ample man­age­able. Spe­cific­ally, the ex­ample does not ad­dress the fol­low­ing topics:

                                                                                                                                                                                              • 错误处理

                                                                                                                                                                                              • Error hand­ling

                                                                                                                                                                                              • 交易

                                                                                                                                                                                              • Trans­ac­tions

                                                                                                                                                                                              • 线程安全

                                                                                                                                                                                              • Thread safety

                                                                                                                                                                                              该示例没有处理错误的托管机制。此时,组件只是将消息吐出到各个控制台窗口中,这对于生产系统来说不是合适的解决方案。对于真正的实施,错误消息应路由到中央控制台,以便它们能够以统一的方式通知操作员。第 11 章“系统管理”(例如,控制总线[xxx])中的系统管理模式满足了这些要求。

                                                                                                                                                                                              The ex­ample has no man­aged mech­an­ism to handle errors. At this point, com­pon­ents simply spit out mes­sages into the vari­ous con­sole win­dowsnot a suit­able solu­tion for a pro­duc­tion system. For a real im­ple­ment­a­tion, error mes­sages should be routed to a cent­ral con­sole so they can notify an op­er­ator in a uni­fied way. The sys­tems man­age­ment pat­terns in Chapter 11, "System Man­age­ment" (e.g., the Con­trol Bus [xxx]) ad­dress these re­quire­ments.

                                                                                                                                                                                              此示例不使用事务队列。例如,如果MessageRouter 在向银行发送四分之二的报价请求消息后崩溃,则某些银行将处理报价请求,而其他银行则不会。同样,如果贷款经纪人在收到所有银行报价回复后但在向客户发送回复之前崩溃,则客户将永远不会收到回复。在现实系统中,此类操作需要封装在事务内,以便在发送相应的出站消息之前不会使用传入消息。

                                                                                                                                                                                              This ex­ample does not use trans­ac­tional queues. For ex­ample, if the Mes­sageRouter crashes after send­ing two out of four quote re­quest mes­sages to the banks, some banks will pro­cess a quote re­quest, while others will not. Like­wise, if the loan broker crashes after it re­ceives all bank quote replies but before it sends a reply to the client, the client will never re­ceive a reply. In a real-life system, ac­tions like these need to be en­cap­su­lated inside trans­ac­tions so that in­com­ing mes­sages are not con­sumed until the cor­res­pond­ing out­bound mes­sage had been sent.

                                                                                                                                                                                              贷款经纪人实现仅在单个线程中执行,并且不担心线程安全。例如,在前一条消息的处理完成之前,不会调用入站消息队列(隐藏在MessageReceiverGatewayBeginReceive 方法。这对于示例应用程序来说很好(并且比同步实现快得多),但对于高吞吐量环境,我们希望使用管理多个执行者线程的消息调度程序。

                                                                                                                                                                                              The loan broker im­ple­ment­a­tion ex­ecutes only in a single thread and does not worry about thread safety. For ex­ample, the Be­gin­Re­ceive method on an in­bound mes­sage queue (hidden away in Mes­sageRe­ceiv­er­Gate­way) is not called until the pro­cess­ing of the pre­vi­ous mes­sage has been com­pleted. This is just fine for an ex­ample ap­plic­a­tion (and a lot faster than the syn­chron­ous im­ple­ment­a­tion), but for a high-through­put en­vir­on­ment, we would want to use a Mes­sage Dis­patcher that man­ages mul­tiple per­former threads.

                                                                                                                                                                                              概括

                                                                                                                                                                                              Sum­mary

                                                                                                                                                                                              本章引导我们完成了使用异步消息队列和 MSMQ 的贷款经纪人应用程序的实现。我们故意不回避展示实现细节,以便揭示构建异步消息应用程序所固有的真正问题。我们更关注设计权衡,而不是特定于供应商的消息 API,因此该示例对于非 C# 开发人员也很有价值。

                                                                                                                                                                                              This chapter walked us through the im­ple­ment­a­tion of the loan broker ap­plic­a­tion using asyn­chron­ous mes­sage queues and MSMQ. We in­ten­tion­ally did not shy away from show­ing im­ple­ment­a­tion de­tails in order to bring the real issues in­her­ent in build­ing asyn­chron­ous mes­saging ap­plic­a­tions to light. We fo­cused on the design trade-offs more so than on the vendor-spe­cific mes­saging API so that the ex­ample is also valu­able for non-C# de­ve­lopers.

                                                                                                                                                                                              这个例子提醒我们实现一个简单的消息应用程序的复杂性。在使用异步消息传递时,在整体应用程序中理所当然的许多事情(例如,调用方法)可能需要大量的编码工作。幸运的是,设计模式为我们提供了一种语言来描述一些设计权衡,而不必深入了解供应商术语。

                                                                                                                                                                                              This ex­ample re­minds us of the com­plex­it­ies of im­ple­ment­ing even a simple mes­saging ap­plic­a­tion. Many things that can be taken for gran­ted in a mono­lithic ap­plic­a­tion (e.g., in­vok­ing a method) can re­quire a sig­ni­fic­ant amount of coding effort when using asyn­chron­ous mes­saging. Luck­ily, the design pat­terns provide us with a lan­guage to de­scribe some of the design trade-offs without having to des­cend too deeply into the vendor jargon.

                                                                                                                                                                                                使用 TIBCO ActiveEnterprise 进行异步实施

                                                                                                                                                                                                Asynchronous Implementation with TIBCO ActiveEnterprise

                                                                                                                                                                                                作者:迈克尔·J·雷蒂格

                                                                                                                                                                                                by Mi­chael J. Rettig

                                                                                                                                                                                                贷款经纪人的前两个实现使用提供基本消息通道功能的集成框架。例如,Axis 和 MSMQ 都提供了 API 来向消息通道发送消息或从消息通道接收消息,而应用程序必须处理几乎所有其他事情。我们特意选择这种类型的实现来演示如何使用常用的 Java 或 C# 库从头开始构建集成解决方案。

                                                                                                                                                                                                The pre­vi­ous two im­ple­ment­a­tions of the loan broker used in­teg­ra­tion frame­works that provided basic Mes­sage Chan­nel func­tion­al­ity. For ex­ample, both Axis and MSMQ provided APIs to send mes­sages to or re­ceive mes­sages from a Mes­sage Chan­nel , while the ap­plic­a­tion had to take care of pretty much everything else. We chose this type of im­ple­ment­a­tion in­ten­tion­ally to demon­strate how an in­teg­ra­tion solu­tion can be built from the ground up, using com­monly avail­able Java or C# lib­rar­ies.

                                                                                                                                                                                                许多商业 EAI 产品套件提供了更多的功能来简化集成解决方案的开发。这些产品套件通常包括可视化开发环境,允许消息转换器和流程管理器的拖放配置。许多还提供复杂的系统管理和元数据管理功能。我们选择 TIBCO ActiveEnterprise 集成套件用于此示例实施。与之前的实现一样,我们主要关注设计决策和权衡,并且仅引入理解解决方案所需的特定于产品的语言。因此,即使您以前没有使用过 TIBCO ActiveEnterprise,本节也应该很有用。如果您对详细的产品或供应商信息感兴趣,请访问http://www.tibco.com

                                                                                                                                                                                                Many com­mer­cial EAI product suites offer sub­stan­tially more func­tion­al­ity to stream­line the de­vel­op­ment of in­teg­ra­tion solu­tions. These product suites typ­ic­ally in­clude visual de­vel­op­ment en­vir­on­ments that allow for drag-drop con­fig­ur­a­tion of Mes­sage Trans­lat­ors and Pro­cess Man­agers. Many also provide soph­ist­ic­ated sys­tems man­age­ment and metadata man­age­ment func­tions. We chose the TIBCO Act­iveEn­ter­prise in­teg­ra­tion suite for this ex­ample im­ple­ment­a­tion. As with the pre­vi­ous im­ple­ment­a­tions, we focus primar­ily on design de­cisions and trade-offs and in­tro­duce only as much product-spe­cific lan­guage as is ne­ces­sary to un­der­stand the solu­tion. There­fore, this sec­tion should be useful even if you have not worked with TIBCO Act­iveEn­ter­prise before. If you are in­ter­ested in de­tailed product or vendor in­form­a­tion, please visit http://www.tibco.com.

                                                                                                                                                                                                此示例实现在解决方案设计上也有所不同,因为它使用了拍卖式的分散-收集方法。此方法使用发布-订阅通道而不是接收者列表,以便贷款经纪人可以向任意数量的银行发送报价请求。这种类型的分散-聚集模式对未知数量的侦听器执行动态请求-应答。此外,贷款经纪人组件的实现使用了 TIBCO流程管理器工具提供的业务流程管理功能。

                                                                                                                                                                                                This ex­ample im­ple­ment­a­tion also dif­fers in the solu­tion design by using an Auc­tion-style Scat­ter-Gather ap­proach. This ap­proach uses a Pub­lish-Sub­scribe Chan­nel in­stead of a Re­cip­i­ent List so that the loan broker can send the quote re­quest to any number of banks. This type of Scat­ter-Gather pat­tern per­forms a dy­namic Re­quest-Reply with an un­known number of listen­ers. Ad­di­tion­ally, the im­ple­ment­a­tion of the loan broker com­pon­ent uses the busi­ness pro­cess man­age­ment func­tion­al­ity provided by TIBCO's Pro­cess Man­ager tool.

                                                                                                                                                                                                解决方案架构

                                                                                                                                                                                                Solu­tion Ar­chi­tec­ture

                                                                                                                                                                                                我们的应用程序是一个简单的银行报价请求系统。客户向贷款经纪人界面提交报价请求。贷款经纪人通过首先获得信用评分来满足请求,然后向多家银行请求报价。一旦贷款经纪人从银行获得报价,它就会选择最佳报价并将其返回给客户(见图)。

                                                                                                                                                                                                Our ap­plic­a­tion is a simple bank quote re­quest system. Cus­tom­ers submit quote re­quests to a loan broker in­ter­face. The loan broker ful­fills the re­quest by first ob­tain­ing a credit score, then re­quest­ing quotes from a number of banks. Once the loan broker ob­tains quotes from the banks, it se­lects the best quote and re­turns it to the cus­tomer (see figure).

                                                                                                                                                                                                系统的客户端期望贷款经纪人有一个同步的请求-答复接口——客户端发送报价请求并等待来自贷款经纪人的答复消息。贷款经纪人又使用请求-答复接口与信用局通信以获得信用评分。收到初始客户端请求后,贷款经纪人可以选择在分布式同步外观后面执行异步操作。这使得贷款经纪人能够利用带有发布-订阅通道的拍卖式分散-收集来获取来自多家银行的银行报价。

                                                                                                                                                                                                Cli­ents of the system expect a syn­chron­ous Re­quest-Reply in­ter­face to the loan brokerthe client sends a quote re­quest and waits for a reply mes­sage from the loan broker. The loan broker in turn uses a Re­quest-Reply in­ter­face to com­mu­nic­ate with the credit bureau to obtain credit scores. Once the ini­tial client re­quest is re­ceived, the loan broker has the option to per­form asyn­chron­ous op­er­a­tions behind the dis­trib­uted, syn­chron­ous facade. This allows the loan broker to util­ize an auc­tion-style Scat­ter-Gather with a Pub­lish-Sub­scribe Chan­nel to ac­quire the bank quotes from mul­tiple banks.

                                                                                                                                                                                                TIBCO 贷款经纪人解决方案架构

                                                                                                                                                                                                TIBCO Loan Broker Solu­tion Ar­chi­tec­ture

                                                                                                                                                                                                图形/09inf29.gif

                                                                                                                                                                                                拍卖序列首先将报价请求消息发布到bank.loan.request 发布-订阅通道,以便任何感兴趣的银行都可以侦听该消息并提供自己的利率。提交回复的银行数量未知,并且每个报价请求的银行数量可能有所不同。拍卖的工作原理是向银行开放拍卖,然后等待预定的时间来获取通道上的响应消息bank.loan.reply。每次收到出价后,超时都会重置,以便其他银行有时间在可能的情况下提交另一个出价。在这种情况下,其他银行的出价是公开的,因此另一家银行实际上可以听取其他出价,并在需要时提出还价。

                                                                                                                                                                                                The auc­tion se­quence begins by pub­lish­ing the re­quest for quotes mes­sage to the bank.loan.re­quest Pub­lish-Sub­scribe Chan­nel so that any in­ter­ested bank can listen for the mes­sage and provide its own rates. The number of banks sub­mit­ting replies is un­known and can vary for each quote re­quest. The auc­tion works by open­ing the auc­tion to the banks, then wait­ing a pre­defined amount of time for re­sponse mes­sages on the chan­nel bank.loan.reply. Each time a bid is re­ceived, the timeout is reset, giving other banks time to submit an­other bid if pos­sible. In this case, the bids of other banks are public, so an­other bank could ac­tu­ally listen for other bids and place a counter bid if de­sired.

                                                                                                                                                                                                贷款经纪人向未知数量的接收者广播报价请求。这与收件人列表完全不同,后者涉及将请求发送到预定义的银行列表。这反映在聚合器的完整性条件中。聚合器不再像以前的实现那样等待每个银行的响应,而是仅依靠超时条件来终止拍卖。聚合只是忽略拍卖超时后收到的任何响应。如果在指定的时间间隔内没有银行回复,聚合器将向客户端发送一条响应消息,表明未获得报价。

                                                                                                                                                                                                The loan broker broad­casts the re­quest for a quote to an un­known number of re­cip­i­ents. This is quite dif­fer­ent from a Re­cip­i­ent List , which in­volves send­ing the re­quest to a pre­defined list of banks. This is re­flec­ted in the Ag­greg­ator's com­plete­ness con­di­tion. In­stead of wait­ing for a re­sponse from each bank as in the pre­vi­ous im­ple­ment­a­tions, the Ag­greg­ator relies solely on a timeout con­di­tion to ter­min­ate the auc­tion. The Ag­greg­ator simply ig­nores any re­sponses re­ceived after the auc­tion times out. If no bank replies within the spe­cified in­ter­val, the Ag­greg­ator sends a re­sponse mes­sage to the client in­dic­at­ing that no quotes were ob­tained.

                                                                                                                                                                                                描述流程管理器行为的活动图

                                                                                                                                                                                                Activ­ity Dia­gram De­scrib­ing the Pro­cess Man­ager Be­ha­vior

                                                                                                                                                                                                图形/09inf30.gif

                                                                                                                                                                                                在此实现中,内容丰富器聚合器功能在流程管理器组件内实现。因此,解决方案架构图没有描述组件之间交互的细节,因为它们嵌入在单个组件中。相反,我们需要查看表示流程管理器使用的流程模板定义的活动图。初始活动图清楚地定义了贷款经纪人的角色,并为流程模板提供了基础。从图中我们可以看出贷款经纪人有几个职责。这可以很好地转化为流程图,以图形方式显示事件和决策路径的确切顺序。

                                                                                                                                                                                                In this im­ple­ment­a­tion, the Con­tent En­richer and Ag­greg­ator func­tions are im­ple­men­ted within the Pro­cess Man­ager com­pon­ent. As a result, the solu­tion ar­chi­tec­ture dia­gram does not de­scribe the de­tails of the in­ter­ac­tion between the com­pon­ents, be­cause they are em­bed­ded in a single com­pon­ent. In­stead, we need to look at the activ­ity dia­gram that rep­res­ents the Pro­cess Tem­plate defin­i­tion used by the Pro­cess Man­ager. An ini­tial activ­ity dia­gram cleanly defines the role of the loan broker and provides the basis for the Pro­cess Tem­plate. From the dia­gram, we can see that the loan broker has sev­eral re­spons­ib­il­it­ies. This trans­lates well into a pro­cess dia­gram, which graph­ic­ally shows the exact order of events and de­cision paths.

                                                                                                                                                                                                实施工具集

                                                                                                                                                                                                The Im­ple­ment­a­tion Tool­set

                                                                                                                                                                                                为了解释我们解决方案的设计,我们需要介绍一些有关 TIBCO 产品套件的基本概念。实施 TIBCO 解决方案通常需要针对特定​​问题评估不同的工具,因为有时可以使用多种不同的工具来解决一个问题。诀窍是选择最好的一个。我们将讨论仅限于构建示例实现所需的 TIBCO 功能:

                                                                                                                                                                                                In order to ex­plain the design of our solu­tion, we need to in­tro­duce some basic con­cepts about the TIBCO product suite. Im­ple­ment­ing TIBCO solu­tions often re­quires eval­u­at­ing dif­fer­ent tools for a par­tic­u­lar prob­lem be­cause at times, a prob­lem can be solved with sev­eral dif­fer­ent tools. The trick is pick­ing the best one. We limit the dis­cus­sion to only those TIBCO fea­tures that are re­quired to con­struct the ex­ample im­ple­ment­a­tion:

                                                                                                                                                                                                • TIB/RendezVous 运输

                                                                                                                                                                                                • TIB/Ren­dez­Vous Trans­port

                                                                                                                                                                                                • TIB/IntegrationManager 流程​​管理器工具

                                                                                                                                                                                                • TIB/In­teg­ra­tion­Man­ager Pro­cess Man­ager tool

                                                                                                                                                                                                • 用于元数据管理的 TIBCO 存储库

                                                                                                                                                                                                • TIBCO Re­pos­it­ory for Metadata Man­age­ment

                                                                                                                                                                                                TIB/RendezVous 运输

                                                                                                                                                                                                TIBCO 消息传递套件的核心是 TIB/RendezVous 传输层。RendezVous 提供了在信息总线上发送和接收 TIBCO 消息的消息传递机制。TIBCO 支持广泛的传输,包括(但不限于)JMS、HTTP、FTP 和电子邮件。RendezVous 为我们示例中的消息提供底层传输。该传输支持同步和异步消息以及点对点通道发布-订阅通道。每个通道可以配置为不同的服务级别:

                                                                                                                                                                                                At the heart of TIBCO's mes­saging suite is the TIB/Ren­dez­Vous trans­port layer. Ren­dez­Vous provides the mes­saging mech­an­ism for send­ing and re­ceiv­ing TIBCO mes­sages on the in­form­a­tion bus. TIBCO sup­ports a wide range of trans­ports, in­clud­ing (but not lim­ited to) JMS, HTTP, FTP, and e-mail. Ren­dez­Vous provides the un­der­ly­ing trans­port for the mes­sages in our ex­ample. The trans­port sup­ports both syn­chron­ous and asyn­chron­ous mes­sages as well as Point-to-Point Chan­nels and Pub­lish-Sub­scribe Chan­nels. Each chan­nel can be con­figured for dif­fer­ent ser­vice levels:

                                                                                                                                                                                                • 可靠消息传递 (RV) 提供高性能,但存在丢失消息的现实风险。

                                                                                                                                                                                                • Re­li­able Mes­saging (RV) provides high per­form­ance, but at the real­istic risk of losing mes­sages.

                                                                                                                                                                                                • 认证消息(RVCM) 至少传送一次。

                                                                                                                                                                                                • Cer­ti­fied mes­sages (RVCM) at least once de­liv­ery.

                                                                                                                                                                                                • 事务性消息传送 (RVTX) 保证一次且仅一次传送。

                                                                                                                                                                                                • Trans­ac­tional mes­saging (RVTX) guar­an­teed once and only once de­liv­ery.

                                                                                                                                                                                                与本书中的 MSMQ 示例类似,TIBCO 提供了一个开放 API,用于使用 Java 或 C++ 创建消息传递解决方案,并包含一系列用于简化开发过程的工具。

                                                                                                                                                                                                Sim­ilar to the MSMQ ex­amples in this book, TIBCO provides an open API for cre­at­ing mes­saging solu­tions using Java or C++ and in­cludes a range of tools for sim­pli­fy­ing the de­vel­op­ment pro­cess.

                                                                                                                                                                                                TIB/IntegrationManager 流程​​管理器工具

                                                                                                                                                                                                TIB/IntegrationManager 是一种 TIBCO 开发工具,由用于设计工作流程解决方案的丰富用户界面和用于执行这些解决方案的流程管理器引擎组成。GUI 提供了广泛的配置、工作流程和实施选项,这些选项存储在 TIBCO 存储库中。TIBCO 使用存储库作为系统的中央配置工件。它保存所有元数据、工作流程和自定义代码。

                                                                                                                                                                                                TIB/In­teg­ra­tion­Man­ager is a TIBCO de­vel­op­ment tool con­sist­ing of a rich user in­ter­face for design­ing work­flow solu­tions and a Pro­cess Man­ager engine for ex­ecut­ing them. The GUI provides an ex­tens­ive array of con­fig­ur­a­tion, work­flow, and im­ple­ment­a­tion op­tions that are stored in the TIBCO Re­pos­it­ory. TIBCO uses the re­pos­it­ory as the cent­ral con­fig­ur­a­tion ar­ti­fact for the system. It holds all metadata, work­flow, and custom code.

                                                                                                                                                                                                该解决方案的工作流组件是其区别于代码级解决方案的一个方面。对于贷款经纪人示例,TIB/IntegrationManager 提供异步和同步消息传递以及基本工作流程,包括服务器会话状态[ EAA ]。出于我们的目的,我们可以将 TIB/IntegrationManager 分为三个部分:通道、作业创建器和流程图。

                                                                                                                                                                                                The work­flow com­pon­ent of the solu­tion is an aspect that sets it apart from code-level solu­tion. For the loan broker ex­ample, TIB/In­teg­ra­tion­Man­ager provides asyn­chron­ous and syn­chron­ous mes­saging as well as basic work­flow, in­clud­ing Server Ses­sion State [EAA]. For our pur­poses, we can break TIB/ In­teg­ra­tion­Man­ager into three parts: the chan­nel, the job cre­ator, and the pro­cess dia­gram.

                                                                                                                                                                                                TIB /IntegrationManager 组件

                                                                                                                                                                                                TIB/In­teg­ra­tion­Man­ager Com­pon­ents

                                                                                                                                                                                                图形/09inf31.gif

                                                                                                                                                                                                TIB/IntegrationManager 中的每个进程都会创建一个“作业”(或进程实例),该“作业”提供用于维护状态的中央会话对象。该对象包括一个带有用于存储对象的 get 和 put 操作的槽环境,以及用于与会话交互的实用方法。简单的 GUI 允许 TIBCO 开发人员创建流程定义(在 TIBCO 中称为流程图)指定作业执行的任务顺序。流程定义类似于 UML 样式的活动图,由通过转换线连接的任务组成(参见下一页的图)。然而,这些图表仅提供了框架;每个活动(即图中的框)背后都有大量的配置和代码。对于我们的示例,我们需要一些代码来处理贷款。TIB/IntegrationManager 使用 ECMAScript(通常称为 JavaScript)作为底层脚本语言。

                                                                                                                                                                                                Every pro­cess in TIB/In­teg­ra­tion­Man­ager cre­ates a "job" (or pro­cess in­stance) that provides a cent­ral ses­sion object for main­tain­ing state. This object in­cludes a slot­ted en­vir­on­ment with get and put op­er­a­tions for stor­ing ob­jects, as well as util­ity meth­ods for in­ter­act­ing with the ses­sion. A simple GUI allows TIBCO de­ve­lopers to create pro­cess defin­i­tions (called Pro­cess Dia­grams in TIBCO) that spe­cify the se­quence of tasks the job ex­ecutes. Pro­cess defin­i­tions re­semble UML-style activ­ity dia­grams con­sist­ing of tasks con­nec­ted by trans­ition lines (see figure on the next page). How­ever, the dia­grams provide only the skel­eton; behind each activ­ity (i.e., box on the dia­gram) is a good amount of con­fig­ur­a­tion and code. For our ex­ample, we re­quire some code for pro­cess­ing the loans. TIB/In­teg­ra­tion­Man­ager uses ECMAScript (com­monly re­ferred to as JavaS­cript) as the un­der­ly­ing script­ing lan­guage.

                                                                                                                                                                                                TIB /IntegrationManager 流程​​图示例

                                                                                                                                                                                                TIB/In­teg­ra­tion­Man­ager Pro­cess Dia­gram Ex­ample

                                                                                                                                                                                                图形/09inf32.gif

                                                                                                                                                                                                典型的流程图包括一系列集成任务。其中包括控制任务(例如分叉、同步条或决策点)、发送和接收消息的信号任务以及执行任务(例如数据转换、路由和系统集成)。此处所示的基本图包括两个任务:执行一些自定义逻辑的 ECMAScript 任务和将消息发布到通道的信号输出任务。任务转换可以包含用于根据消息内容或其他标准选择特定路由的逻辑。

                                                                                                                                                                                                A typ­ical pro­cess dia­gram in­cludes a series of in­teg­ra­tion tasks. These in­clude con­trol tasks such as forks, sync bars or de­cision points, signal tasks to send and re­ceive mes­sages, and ex­e­cu­tion tasks such as data trans­la­tion, rout­ing, and system in­teg­ra­tion. The basic dia­gram pic­tured here in­cludes two tasks: an ECMAScript task that ex­ecutes some custom logic and a signal-out task that pub­lishes a mes­sage to a chan­nel. Task trans­itions can con­tain logic for se­lect­ing a par­tic­u­lar route based upon mes­sage con­tents or other cri­teria.

                                                                                                                                                                                                用于元数据管理的 TIBCO 存储库

                                                                                                                                                                                                集成和消息传递几乎总是需要某种形式的自描述数据(请参阅第 8 章“消息转换”中的“简介”)。TIBCO 将元数据类定义为存储在 TIBCO 存储库中的 ActiveEnterprise (AE) 对象。通过消息通道发送的每个 AE 对象都包含一个格式指示符,以指示该消息遵循的类定义。

                                                                                                                                                                                                In­teg­ra­tion and mes­saging nearly always re­quires some form of self-de­scrib­ing data (see "In­tro­duc­tion" in Chapter 8, "Mes­sage Trans­form­a­tion"). TIBCO defines metadata classes as Act­iveEn­ter­prise (AE) ob­jects stored in the TIBCO re­pos­it­ory. Every AE object sent across a mes­sage chan­nel in­cludes a Format In­dic­ator to in­dic­ate the class defin­i­tion that this mes­sage ad­heres to.

                                                                                                                                                                                                管理消息元数据是开发过程的重要组成部分。TIBCO 开发人员可以直接在开发环境中定义元数据,可以从关系数据库等外部系统中提取元数据(使用通道适配器 [ xxx] 中描述的元数据适配器),或者导入 XML 模式。元数据为系统中的对象和消息提供了明确的契约。一旦在 TIBCO 存储库中定义了类,这些对象就可以在 ECMA 脚本中进行实例化和操作:

                                                                                                                                                                                                Man­aging mes­sage metadata is an im­port­ant part of the de­vel­op­ment pro­cess. TIBCO de­ve­lopers can define the metadata dir­ectly within the de­vel­op­ment en­vir­on­ment, can ex­tract it from ex­ternal sys­tems such as re­la­tional data­bases (using a Metadata Ad­apter as de­scribed in Chan­nel Ad­apter [xxx]), or import XML schemas. The metadata provides an ex­pli­cit con­tract for ob­jects and mes­sages in the system. Once the classes are defined in the TIBCO re­pos­it­ory, the ob­jects are avail­able for in­stan­ti­ation and ma­nip­u­la­tion within ECMA script:

                                                                                                                                                                                                //TIBCO AE类的实例化
                                                                                                                                                                                                var Bank = new aeclass.BankQuoteRequest();
                                                                                                                                                                                                银行.CorrelationID = job.generateGUID();
                                                                                                                                                                                                银行.SSN = 工作.请求.SSN;
                                                                                                                                                                                                
                                                                                                                                                                                                //In­stan­ti­ation of a TIBCO AE class
                                                                                                                                                                                                var bank = new ae­class.BankQuote­Re­quest();
                                                                                                                                                                                                bank.Cor­rel­a­tionID = job.gen­er­ateG­UID();
                                                                                                                                                                                                bank.SSN = job.re­quest.SSN;
                                                                                                                                                                                                

                                                                                                                                                                                                但是,请记住,这是一个动态的脚本环境,编译时类型检查有限。更改元数据定义很容易破坏系统的其他部分。如果没有适当的测试和开发实践,基于消息的系统很容易成为“仅添加”系统,这意味着元数据仅添加到系统中并且永远不会更改,因为担心会破坏某些内容。

                                                                                                                                                                                                How­ever, re­mem­ber that this is a dy­namic, scrip­ted en­vir­on­ment with lim­ited com­pile-time type check­ing. Chan­ging a metadata defin­i­tion can easily break an­other part of your system. Without proper test­ing and de­vel­op­ment prac­tices, mes­sage-based sys­tems can easily become "add-only" sys­tems, mean­ing metadata is only added to the system and never changed for fear of break­ing some­thing.

                                                                                                                                                                                                接口

                                                                                                                                                                                                The In­ter­faces

                                                                                                                                                                                                第 446 页的解决方案架构图向我们展示了该解决方案需要以下服务。

                                                                                                                                                                                                The Solu­tion Ar­chi­tec­ture dia­gram, on page 446, shows us that the solu­tion re­quires the fol­low­ing ser­vices.

                                                                                                                                                                                                贷款经纪人

                                                                                                                                                                                                Loan Broker

                                                                                                                                                                                                • 收到初始请求

                                                                                                                                                                                                • Re­ceives the ini­tial re­quest

                                                                                                                                                                                                • 获得信用评分

                                                                                                                                                                                                • Ob­tains a credit score

                                                                                                                                                                                                • 与银行举行贷款拍卖

                                                                                                                                                                                                • Holds loan auc­tion with banks

                                                                                                                                                                                                • 返回最佳贷款报价给客户

                                                                                                                                                                                                • Re­turns best loan offer to client

                                                                                                                                                                                                信用服务

                                                                                                                                                                                                Credit Ser­vice

                                                                                                                                                                                                • 提供基于 SSN 的信用评分

                                                                                                                                                                                                • Provides a credit score based upon SSN

                                                                                                                                                                                                银行

                                                                                                                                                                                                Bank(s)

                                                                                                                                                                                                • 根据信用评级和贷款金额提交报价

                                                                                                                                                                                                • Submit a quote based upon the credit rating and loan amount

                                                                                                                                                                                                每个服务都可以通过外部接口访问。对于每个这样的接口,我们必须做出以下设计决策:

                                                                                                                                                                                                Each ser­vice is ac­cess­ible through an ex­ternal in­ter­face. For each such in­ter­face, we have to make the fol­low­ing design de­cisions:

                                                                                                                                                                                                • 对话风格:同步与异步

                                                                                                                                                                                                • Con­ver­sa­tion Style: Syn­chron­ous vs. Asyn­chron­ous

                                                                                                                                                                                                • 服务质量水平

                                                                                                                                                                                                • Qual­ity of Ser­vice Level

                                                                                                                                                                                                界面的对话风格已由解决方案架构预先确定。贷款经纪人和信贷服务都需要同步接口,而与银行的通信将是纯粹异步的。

                                                                                                                                                                                                The con­ver­sa­tion styles for the in­ter­faces have been pre­de­ter­mined by the solu­tion ar­chi­tec­ture. The loan broker and credit ser­vice will both need syn­chron­ous in­ter­faces, while the com­mu­nic­a­tion with the banks will be purely asyn­chron­ous.

                                                                                                                                                                                                消息传递解决方案的服务级别可能变得相当复杂,尤其是在故障转移场景下。幸运的是,贷款示例服务级别解析为一个简单的解决方案。如果发生故障(超时、消息丢失、系统停机),可以重新提交原始请求(贷款经纪人是幂等接收者[xxx])。请记住,我们此时仅获取报价。它还不是具有法律约束力的协议。当然,这种类型的假设需要记录在系统中并被所有相关方理解。例如,银行需要知道报价请求可能会再次重新提交。如果银行出于欺诈检测目的跟踪客户贷款请求,我们可能必须更改解决方案以避免发送重复请求,例如使用保证交付

                                                                                                                                                                                                Ser­vice levels for mes­saging solu­tions can become quite com­plic­ated, es­pe­cially given fail­over scen­arios. For­tu­nately, the loan ex­ample ser­vice level re­solves to a simple solu­tion. In event of fail­ure (timeout, dropped mes­sages, down sys­tems), the ori­ginal re­quest can be re­sub­mit­ted (the loan broker is an Idem­potent Re­ceiver [xxx]). Re­mem­ber that we are only ob­tain­ing a quote at this point. It is not yet a leg­ally bind­ing agree­ment. Of course, this type of as­sump­tion needs to be doc­u­mented in the system and un­der­stood by all parties in­volved. For ex­ample, the banks need to know that a quote re­quest may be re­sub­mit­ted a second time. If the banks track cus­tomer loan re­quests for fraud de­tec­tion pur­poses, we may have to change the solu­tion to avoid send­ing du­plic­ate re­quests, for ex­ample by using Guar­an­teed De­liv­ery.

                                                                                                                                                                                                实现同步服务

                                                                                                                                                                                                Im­ple­ment­ing the Syn­chron­ous Ser­vices

                                                                                                                                                                                                贷款经纪人系统在客户和贷款经纪人之间以及贷款经纪人和征信局之间有两个同步接口。TIBCO 通过使用Request-ReplyCommand Message的操作来实现 RPC 风格的消息传递。本质上,这是底层 TIBCO 消息传递引擎的同步包装器。在调用 TIBCO 操作期间,您可以侦听通过总线传递的请求和回复消息。请求消息发布在指定通道上(例如customer.loan.request )。该消息包含返回地址它指定用于回复的所谓 INBOX 通道的地址。TIBCO 将异步细节隐藏在 RPC 风格的编程模型后面。在 TIB/IntegrationManager 中,诸如获取信用评分之类的域操作被定义为 AE 类的一部分,并且可以从流程建模工具中调用。例如,要将贷款经纪人公开为同步接口,我们必须执行几个实现步骤。信用局服务的实施严格遵循这些相同的步骤。

                                                                                                                                                                                                The loan broker system has two syn­chron­ous in­ter­faces­between the cus­tomer and the loan broker and between the loan broker and the credit bureau. TIBCO im­ple­ments RPC-style mes­saging with op­er­a­tions using Re­quest-Reply and Com­mand Mes­sage. Es­sen­tially, this is a syn­chron­ous wrap­per to the un­der­ly­ing TIBCO mes­saging engine. During the in­voc­a­tion of TIBCO op­er­a­tions, you can listen to the re­quest and reply mes­sages passing across the bus. The re­quest mes­sage is pub­lished on the spe­cified chan­nel (for ex­ample, cus­tomer.loan.re­quest). The mes­sage in­cludes a Return Ad­dress that spe­cifies the ad­dress of a so-called INBOX chan­nel for the reply. TIBCO hides the asyn­chron­ous de­tails behind an RPC-style pro­gram­ming model. In TIB/In­teg­ra­tion­Man­ager, the domain op­er­a­tions such as obtain credit score are defined as part of the AE classes and can be in­voked from the pro­cess mod­el­ing tool. For ex­ample, to expose the loan broker as a syn­chron­ous in­ter­face, we must per­form sev­eral im­ple­ment­a­tion steps. The im­ple­ment­a­tion of the credit bureau ser­vice fol­lows closely to these same steps.

                                                                                                                                                                                                定义 AE 类定义

                                                                                                                                                                                                AE 类定义通过 TIB/RendezVous 通道发送的消息的数据格式。在 TIB/IntegrationManager 中定义类与在您最喜欢的 IDE 中创建类有很大不同。AE 类是通过 TIB/IntegrationManager IDE 中的一系列对话框(见图)定义的。对话框允许您为类选择名称,然后为类指定字段。这些字段可以输入为整数、浮点数或双精度数,也可以由其他 AE 类组成。

                                                                                                                                                                                                AE classes define the data format for mes­sages sent across TIB/Ren­dez­Vous chan­nels. De­fin­ing a class in TIB/In­teg­ra­tion­Man­ager is quite dif­fer­ent from cre­at­ing a class in your fa­vor­ite IDE. AE classes are defined through a series of dialog boxes (see figure) from the TIB/In­teg­ra­tion­Man­ager IDE. The dialog boxes allow you to select a name for your class and then des­ig­nate fields for the class. The fields can be typed as in­tegers, floats, or doubles, or can be com­posed of other AE classes.

                                                                                                                                                                                                使用属性和操作定义AE类

                                                                                                                                                                                                De­fin­ing an AE Class with At­trib­utes and Op­er­a­tions

                                                                                                                                                                                                图形/09inf33.jpg

                                                                                                                                                                                                定义 AE 操作定义

                                                                                                                                                                                                与添加接口方法类似,可以将操作添加到 AE 类中。可以在定义中指定参数和返回类型。对于接口来说,没有指定任何实现。当我们使用作业创建器将通道绑定到流程实例时,会稍后绑定实现。

                                                                                                                                                                                                Sim­ilar to adding in­ter­face meth­ods, op­er­a­tions can be added to an AE class. The para­met­ers and the return types can be spe­cified in the defin­i­tion. True to an in­ter­face, no im­ple­ment­a­tion is spe­cified. The im­ple­ment­a­tion is bound later when we use the job cre­ator to bind a chan­nel to a pro­cess in­stance.

                                                                                                                                                                                                创建流程图

                                                                                                                                                                                                流程图提供了操作的实现。本例中实现的操作需要返回参数。与代码中实现的方法不同,流程图没有“返回”值。相反,我们指定作业中将放置返回值的槽。我们在作业创建者中指定了这一点,并且我们必须记住在流程图中将值正确分配给作业槽。流程图的实际实现将在以下几页中详细讨论。

                                                                                                                                                                                                A pro­cess dia­gram provides the im­ple­ment­a­tion for the op­er­a­tion. The op­er­a­tions im­ple­men­ted in this ex­ample re­quire a return para­meter. Unlike meth­ods im­ple­men­ted in code, a pro­cess dia­gram doesn't have a "return" value. In­stead, we spe­cify the slot in the job where the return value will be placed. We des­ig­nate this in the job cre­ator, and we must re­mem­ber to prop­erly assign the value to the job slot in our pro­cess dia­gram. The actual im­ple­ment­a­tion of the pro­cess dia­gram is dis­cussed in detail in the fol­low­ing pages.

                                                                                                                                                                                                创建客户端/服务器通道来接收请求

                                                                                                                                                                                                该通道允许我们定义传输、消息定义、服务和主题。对于我们的同步操作,我们需要一个客户端/服务器通道。我们可以指定在步骤 1 中创建的 AE 类。从最初的接口定义中,我们选择了具有可靠消息传递的 RendezVous。配置这些选项只需单击并选择适当的选项即可。

                                                                                                                                                                                                The chan­nel allows us to define our trans­port, mes­sage defin­i­tion, ser­vice, and sub­ject. For our syn­chron­ous op­er­a­tions, we need a client/server chan­nel. We can spe­cify the AE classes that we cre­ated in step 1. From our ini­tial in­ter­face defin­i­tions, we chose Ren­dez­Vous with re­li­able mes­saging. Con­fig­ur­ing these op­tions is just a matter of click­ing and se­lect­ing the proper op­tions.

                                                                                                                                                                                                定义通道属性

                                                                                                                                                                                                De­fin­ing Chan­nel Prop­er­ties

                                                                                                                                                                                                图形/09inf34.jpg

                                                                                                                                                                                                配置作业创建器以实例化流程图

                                                                                                                                                                                                作业创建者从通道检索值,并通过创建作业并初始化其环境槽将它们传递到流程图。创建作业后,流程图就会被实例化。执行将遵循活动图定义的路径。流程完成后,作业创建者会将回复返回给通道。我们可以在作业创建者对话框中看到我们操作的名称。

                                                                                                                                                                                                The job cre­ator re­trieves values from the chan­nel and passes them to the pro­cess dia­gram by cre­at­ing a job and ini­tial­iz­ing its en­vir­on­ment slots. Once the job is cre­ated, the pro­cess dia­gram is in­stan­ti­ated. Ex­e­cu­tion will follow the path defined by the activ­ity dia­gram. Once the pro­cess fin­ishes, the job cre­ator re­turns the reply back to the chan­nel. We can see in the job cre­ator dialog the name of our op­er­a­tion.

                                                                                                                                                                                                配置作业创建者

                                                                                                                                                                                                Con­fig­ur­ing the Job Cre­ator

                                                                                                                                                                                                图形/09inf35.jpg

                                                                                                                                                                                                贷款经纪人流程

                                                                                                                                                                                                The Loan Broker Pro­cess

                                                                                                                                                                                                有了我们的同步服务,我们就可以实施贷款经纪人将一切联系在一起。所包含的图表代表了贷款经纪人的流程。该过程定义了贷款经纪人组件的行为。在之前的步骤中,我们定义了 AE 操作、通道和作业创建者,以便在客户端向 CreditRequest 通道提交消息时实例化流程图。

                                                                                                                                                                                                With our syn­chron­ous ser­vices in place, we can im­ple­ment the loan broker to tie everything to­gether. The in­cluded dia­gram rep­res­ents the loan broker pro­cess. This pro­cess defines the be­ha­vior of the loan broker com­pon­ent. In our prior steps, we defined the AE op­er­a­tion, chan­nel, and job cre­ator to in­stan­ti­ate the pro­cess dia­gram when a client sub­mits a mes­sage to the CreditRe­quest chan­nel.

                                                                                                                                                                                                设计注意事项

                                                                                                                                                                                                从设计的角度来看,我们需要非常小心流程图中包含的内容。大型、复杂的图表很快就会变得难以管理。在图表中创建有效的关注点分离至关重要,避免单个流程定义中业务逻辑和流程逻辑的丑陋混合。定义什么是流程逻辑和什么是业务逻辑很困难。一个好的经验法则是将流程逻辑视为外部系统交互:接下来我要连接到哪个系统?如果系统不可用怎么办?业务逻辑通常会涉及更多特定领域的语言:如何激活订单?如何计算信用评分?如何创建银行贷款报价?考虑到业务代码的复杂性,功能齐全的开发语言往往是实现的最佳选择。大多数流程管理工具(包括 TIB/IntegrationManager)允许您直接与 Java 或其他语言集成。

                                                                                                                                                                                                From a design per­spect­ive, we need to be very care­ful as to what is in­cluded in the pro­cess dia­gram. Large, com­plex dia­grams quickly become un­man­age­able. It is crit­ical to create an ef­fect­ive sep­ar­a­tion of con­cerns within the dia­grams, avoid­ing an ugly mix­ture of busi­ness logic and pro­cess logic inside a single pro­cess defin­i­tion. De­fin­ing what is pro­cess logic and what is busi­ness logic is hard. A good rule of thumb is to think of pro­cess logic as ex­ternal system in­ter­ac­tion: What system do I con­nect to next? What do I do if the system is not avail­able? Busi­ness logic will typ­ic­ally in­volve more domain-spe­cific lan­guage: How do I ac­tiv­ate an order? How do I cal­cu­late a credit score? How do I create a quote for a bank loan? Given the com­plex nature of busi­ness code, a full-fea­tured de­vel­op­ment lan­guage is often the best choice for im­ple­ment­a­tion. Most pro­cess man­age­ment tools, in­clud­ing TIB/In­teg­ra­tion­Man­ager, allow you to in­teg­rate dir­ectly with Java or an­other lan­guage.

                                                                                                                                                                                                对于那些熟悉 MVC(模型、视图、控制器)概念(例如,[ POSA ])的人来说,可以在类似的上下文中查看实现。通过简单地将视图重命名为工作流,我们的实现就陷入了简洁的定义。

                                                                                                                                                                                                For those fa­mil­iar with the MVC (Model, View, Con­trol­ler) concept (for ex­ample, [POSA]), the im­ple­ment­a­tion can be viewed in a sim­ilar con­text. By simply re­nam­ing view to work­flow, our im­ple­ment­a­tion falls into a con­cise defin­i­tion.

                                                                                                                                                                                                1. 工作流是 工作流的可视化模型。

                                                                                                                                                                                                2. Work­flow a visu­al­ized model of the work­flow.

                                                                                                                                                                                                3. 控制器是 从消息总线接收事件的流程引擎,执行流程工作流的适当组件。

                                                                                                                                                                                                4. Con­trol­ler the pro­cess engine that re­ceives events from the mes­sage bus, ex­ecut­ing the proper com­pon­ent of the pro­cess work­flow.

                                                                                                                                                                                                5. 对底层业务代码(ECMAScript、JavaScript、Java、J2EE 等)进行建模。

                                                                                                                                                                                                6. Model the un­der­ly­ing busi­ness code (ECMAScript, JavaS­cript, Java, J2EE, etc.).

                                                                                                                                                                                                流程模型的实现

                                                                                                                                                                                                下图包含脚本执行框和执行集成操作的自定义任务的组合。使用可视化流程建模工具对流程进行建模的一大优势是运行的“代码”看起来与我们用来设计解决方案的 UML 活动图非常相似。

                                                                                                                                                                                                The fol­low­ing dia­gram con­tains a mix of script ex­e­cu­tion boxes and custom tasks that per­form in­teg­ra­tion ac­tions. One of the big ad­vant­ages of mod­el­ing pro­cesses using a visual pro­cess mod­el­ing tool is that the run­ning "code" looks very sim­ilar to the UML activ­ity dia­gram that we used to design the solu­tion.

                                                                                                                                                                                                贷款经纪人流程定义

                                                                                                                                                                                                The Loan Broker Pro­cess Defin­i­tion

                                                                                                                                                                                                图形/09inf36.jpg

                                                                                                                                                                                                由于每个任务图标都可以包含实际代码或重要的配置参数,因此我们将逐步介绍每个任务并更详细地描述它。

                                                                                                                                                                                                Since each task icon can con­tain actual code or im­port­ant con­fig­ur­a­tion para­met­ers, we'll walk through each task and de­scribe it in more detail.

                                                                                                                                                                                                第一个框代表一个 ECMAScript,它实例化AE对象并向字段分配值。

                                                                                                                                                                                                The first box rep­res­ents an ECMAScript that in­stan­ti­ates an AE object and as­signs values to the fields.

                                                                                                                                                                                                var Credit = new aeclass.CreditBureauRequest();
                                                                                                                                                                                                信用.SSN = 工作.请求.SSN;
                                                                                                                                                                                                job.creditRequest = 信用;
                                                                                                                                                                                                
                                                                                                                                                                                                var credit = new ae­class.Cred­it­Bur­eauRe­quest();
                                                                                                                                                                                                credit.SSN = job.re­quest.SSN;
                                                                                                                                                                                                job.creditRe­quest = credit;
                                                                                                                                                                                                

                                                                                                                                                                                                创建的信用请求需要作为下一个活动的参数,该活动将调用对信用局的同步操作调用。信用局作为一个单独的流程图实现,由接收消息并返回信用评分的单个 ECMA 任务组成。同步操作意味着贷款经纪人进程将等待信用局的回复消息到达。

                                                                                                                                                                                                The credit re­quest cre­ated is needed as a para­meter in the next activ­ity, which in­vokes the syn­chron­ous op­er­a­tions call to the credit bureau. The credit bureau is im­ple­men­ted as a sep­ar­ate pro­cess dia­gram con­sist­ing of a single ECMA task that re­ceives the mes­sage and re­turns a credit score. The syn­chron­ous op­er­a­tion im­plies that the Loan Broker pro­cess will wait until a reply mes­sage from the credit bureau ar­rives.

                                                                                                                                                                                                一旦贷款经纪人收到信用局的回复,我们可以在图中看到需要创建另一个 AE 对象来向任何参与银行发布报价请求。为了实现此功能,我们使用Mapper任务,它允许我们以图形方式映射来自多个源的数据项。映射器是消息转换器模式的可视化实现。Mapper任务展示了管理元数据的优点之一。由于 TIB/IntegrationManager 可以访问定义每个对象结构的元数据,因此Mapper显示源对象和目标对象的结构,并允许我们通过单击几下鼠标直观地映射字段。使用输入对象的值实例化一个新对象。我们使用作业对象的generateGUID 方法生成唯一的相关标识符并将其分配给bankRequest。正如我们稍后将看到的,关联 ID 字段非常重要,它允许我们同时处理多个贷款请求。

                                                                                                                                                                                                Once the loan broker re­ceives a reply from the credit bureau, we can see in the dia­gram that an­other AE object needs to be cre­ated to pub­lish a quote re­quest to any par­ti­cip­at­ing banks. To im­ple­ment this func­tion, we use the Mapper task that allows us to graph­ic­ally map data items from mul­tiple sources. The Mapper is a visual im­ple­ment­a­tion of the Mes­sage Trans­lator pat­tern. The Mapper task demon­strates one of the ad­vant­ages of man­aging metadata. Be­cause TIB/In­teg­ra­tion­Man­ager has access to the metadata de­fin­ing the struc­ture of each object, the Mapper dis­plays the struc­ture of the source and target ob­jects and allows us to visu­ally map the fields with a few mouse clicks. A new object is in­stan­ti­ated with the values from the input object. We use the job object's gen­er­ateG­UID method to gen­er­ate a unique Cor­rel­a­tion Iden­ti­fier and assign it to the Cor­rel­a­tion ID field of the bankRe­quest object. As we will see later, the Cor­rel­a­tion ID field is im­port­ant to allow us to pro­cess more than one loan re­quest at the same time.

                                                                                                                                                                                                视觉映射器任务创建银行请求消息

                                                                                                                                                                                                The Visual Mapper Task Cre­ates the Bank Re­quest Mes­sage

                                                                                                                                                                                                图形/09inf37.jpg

                                                                                                                                                                                                我们可以使用 ECMAScript 任务来完成相同的功能,而不是使用可视映射器。等效代码如下所示。您可以看到我们如何创建 BankQuoteRequest 类型的新对象分配源对象中的每个字段:

                                                                                                                                                                                                In­stead of using the visual Mapper, we could have used an ECMAScript task to ac­com­plish the same func­tion. The equi­val­ent code would look like the fol­low­ing. You can see how we create a new object of type BankQuote­Re­quest and assign each field from the source ob­jects:

                                                                                                                                                                                                var Bank = new aeclass.BankQuoteRequest();
                                                                                                                                                                                                //创建ID来唯一标识本次交易。
                                                                                                                                                                                                // 稍后我们将需要它来过滤回复
                                                                                                                                                                                                银行.CorrelationID = job.generateGUID();
                                                                                                                                                                                                银行.SSN = 工作.请求.SSN;
                                                                                                                                                                                                银行.CreditScore = job.creditReply.CreditScore;
                                                                                                                                                                                                银行.HistoryLength = job.creditReply.HistoryLength;
                                                                                                                                                                                                银行.LoanAmount = job.request.LoanAmount;
                                                                                                                                                                                                银行.LoanTerm = job.request.LoanTerm;
                                                                                                                                                                                                
                                                                                                                                                                                                job.bankRequest = 银行;
                                                                                                                                                                                                
                                                                                                                                                                                                var bank = new ae­class.BankQuote­Re­quest();
                                                                                                                                                                                                //Create ID to uniquely identify this trans­ac­tion.
                                                                                                                                                                                                // We will need this later to filter replies
                                                                                                                                                                                                bank.Cor­rel­a­tionID = job.gen­er­ateG­UID();
                                                                                                                                                                                                bank.SSN = job.re­quest.SSN;
                                                                                                                                                                                                bank.Cred­itScore = job.creditReply.Cred­itScore;
                                                                                                                                                                                                bank.His­toryLength = job.creditReply.His­toryLength;
                                                                                                                                                                                                bank.LoanAmount = job.re­quest.LoanAmount;
                                                                                                                                                                                                bank.LoanTerm = job.re­quest.LoanTerm;
                                                                                                                                                                                                
                                                                                                                                                                                                job.bankRe­quest = bank;
                                                                                                                                                                                                

                                                                                                                                                                                                是否使用可视化Mapper任务或 ECMAScript 任务来实现消息转换器功能取决于映射的类型和复杂性。Mapper任务可以为我们提供源对象和目标对象之间连接的良好视觉视图,但当对象具有许多字段时,可能会变得难以阅读。另一方面,映射器包含特殊函数,允许我们用一行映射重复字段(即数组),而不必编写循环代码。

                                                                                                                                                                                                Whether to use the visual Mapper task or an ECMAScript task to im­ple­ment a Mes­sage Trans­lator func­tion de­pends on the type and com­plex­ity of a map­ping. The Mapper task can give us a nice visual view of the con­nec­tions between source and target ob­jects but can become hard to read when the ob­jects have many fields. On the other hand, the Mapper in­cludes spe­cial func­tions that allow us to map re­peat­ing fields (i.e., Arrays) with a single line in­stead of having to code a loop.

                                                                                                                                                                                                接下来,一个非常简单的 ECMAScript 任务实例化一个出价数组,该数组将保存传入的银行响应。该脚本仅包含一行:

                                                                                                                                                                                                Next, a very simple ECMAScript task in­stan­ti­ates a bid array that will hold the in­com­ing bank re­sponses. This script con­tains only a single line:

                                                                                                                                                                                                job.bids = new Array();
                                                                                                                                                                                                
                                                                                                                                                                                                job.bids = new Array();
                                                                                                                                                                                                

                                                                                                                                                                                                下一个任务是 Signal Out 任务,它将bankRequest对象发布到bank.loan.request通道。这是一个异步操作,因此该过程立即转换到下一步,而无需等待回复。

                                                                                                                                                                                                The next task is a Signal Out task that pub­lishes the bankRe­quest object to the bank.loan.re­quest chan­nel. This is an asyn­chron­ous action, so the pro­cess im­me­di­ately trans­itions to the next step without wait­ing for a reply.

                                                                                                                                                                                                以下任务等待bank.loan 上传入的报价回复消息。回复频道。如果在指定的超时间隔内收到消息,脚本任务会将收到的消息添加到 bids 数组中:

                                                                                                                                                                                                The fol­low­ing tasks waits for in­com­ing quote reply mes­sages on the bank.loan. reply chan­nel. If a mes­sage is re­ceived within the spe­cified timeout in­ter­val, the script task adds the re­ceived mes­sage to the bids array:

                                                                                                                                                                                                job.bids[job.bids.length] = job.loanReply;
                                                                                                                                                                                                
                                                                                                                                                                                                job.bids[job.bids.length] = job.loan­Reply;
                                                                                                                                                                                                

                                                                                                                                                                                                拍卖期结束后,信号输入任务超时并将流程转换到最终任务。此 ECMAScript 任务实现 Aggregator 的聚合算法,从所有出价中选择最佳报价。该代码创建 LoanQuoteReply 的新实例SSNLoanAmount字段从请求对象传输到此回复对象,并循环遍历bids数组以查找最佳报价。

                                                                                                                                                                                                Once the auc­tion period ends, the Signal In task times out and trans­itions the pro­cess to the final task. This ECMAScript task im­ple­ments the ag­greg­a­tion al­gorithm of the Ag­greg­ator , se­lect­ing the best quote from all the bids. The code cre­ates a new in­stance of the Loan­QuoteReply, trans­fers the SSN and LoanAmount fields from the re­quest object to this reply object, and loops through the bids array to find the best quote.

                                                                                                                                                                                                var LoanReply = new aeclass.LoanQuoteReply();
                                                                                                                                                                                                LoanReply.SSN = job.request.SSN;
                                                                                                                                                                                                LoanReply.LoanAmount = job.request.LoanAmount;
                                                                                                                                                                                                
                                                                                                                                                                                                var bids = job.bids;
                                                                                                                                                                                                for(var i = 0; i < bids.length; i++){
                                                                                                                                                                                                    var item = bids[i];
                                                                                                                                                                                                    if(i == 0 || (item.InterestRate <loanReply.InterestRate)){
                                                                                                                                                                                                        LoanReply.InterestRate = item.InterestRate;
                                                                                                                                                                                                        LoanReply.QuoteID = item.QuoteID;
                                                                                                                                                                                                    }
                                                                                                                                                                                                }
                                                                                                                                                                                                
                                                                                                                                                                                                job.loanReply=loanReply;
                                                                                                                                                                                                
                                                                                                                                                                                                var loan­Reply = new ae­class.Loan­QuoteReply();
                                                                                                                                                                                                loan­Reply.SSN = job.re­quest.SSN;
                                                                                                                                                                                                loan­Reply.LoanAmount = job.re­quest.LoanAmount;
                                                                                                                                                                                                
                                                                                                                                                                                                var bids = job.bids;
                                                                                                                                                                                                for(var i = 0; i < bids.length; i++){
                                                                                                                                                                                                    var item = bids[i];
                                                                                                                                                                                                    if(i == 0 || (item.In­teres­tRate < loan­Reply.In­teres­tRate)){
                                                                                                                                                                                                        loan­Reply.In­teres­tRate = item.In­teres­tRate;
                                                                                                                                                                                                        loan­Reply.QuoteID = item.QuoteID;
                                                                                                                                                                                                    }
                                                                                                                                                                                                }
                                                                                                                                                                                                
                                                                                                                                                                                                job.loan­Reply = loan­Reply;
                                                                                                                                                                                                

                                                                                                                                                                                                ECMA 脚本的最后一行将要返回给客户的贷款回复对象放入作业槽中。我们将贷款经纪人作业创建者配置为将作业的LoanReply属性作为回复消息返回给客户(见图)。当该过程完成时,作业创建者会拉取在作业的LoanReply属性中找到的消息,并将该值返回给客户端。

                                                                                                                                                                                                The last line of the ECMA script places the loan reply object to be re­turned to cus­tomer into a job slot. We con­figured the loan broker job cre­ator to return the loan­Reply at­trib­ute of the job to the cus­tomer as the reply mes­sage (see figure). When the pro­cess fin­ishes, the job cre­ator pulls the mes­sage found in the loan­Reply at­trib­ute of the job, and re­turns the value to the client.

                                                                                                                                                                                                配置 Job Creator 的规则集以返回最佳报价

                                                                                                                                                                                                Con­fig­ur­ing the Job Cre­ator's Rule Set to Return the Best Quote

                                                                                                                                                                                                图形/09inf38.jpg

                                                                                                                                                                                                管理并发拍卖

                                                                                                                                                                                                Man­aging Con­cur­rent Auc­tions

                                                                                                                                                                                                拍卖的实施带来了一些发展障碍。并发性在一定程度上增加了问题的复杂性。为了使拍卖正常进行,我们需要发布异步消息,然后等待指定的时间以获得回复。但是,如果同时发生多个拍卖,我们必须确保每个贷款经纪人只收到其关注的回复,而不是所有回复。

                                                                                                                                                                                                The im­ple­ment­a­tion of the auc­tion provided a few de­vel­op­ment hurdles. Con­cur­rency adds a degree of com­plex­ity to the prob­lem. For the auc­tion to work, we need to pub­lish an asyn­chron­ous mes­sage, then wait a spe­cified amount of time for replies. How­ever, if mul­tiple auc­tions occur at once, we must ensure that each loan broker re­ceives only the replies it is con­cerned about, not all the replies.

                                                                                                                                                                                                此功能是使用相关标识符和选择性消费者来实现的丢弃不相关的消息。理想情况下,我们希望这种过滤发生在通道级别,而不是流程实例。然而,这需要在运行时动态创建通道主题,每个通道主题对应一个待处理的拍卖。目前,TIB/IntegrationManager 中存在实现限制,阻止我们使用此方法。当流程图在运行时实例化时,它需要注册它正在侦听的所有主题。这允许流程引擎侦听有关这些主题的任何消息并根据需要对它们进行排队。排队可以防止由于流程图在一个主题上发布然后转换到另一状态以侦听答复而导致的计时错误。在异步世界中,如果转换时间太长,在流程图有机会订阅之前可以收到回复。因此,流程图可以方便地对入站消息进行排队,但代价是不允许动态主题。考虑到本示例的要求,过程级别的过滤效果非常好。这相关标识符是分配给每个流程的唯一标识符,然后传递给每个银行流程并包含在每个回复中。单行 ECMAScript 在Signal In任务中提供过滤:

                                                                                                                                                                                                This func­tion­al­ity was im­ple­men­ted using a Cor­rel­a­tion Iden­ti­fier and a Se­lect­ive Con­sumer to dis­card un­re­lated mes­sages. Ideally, we would want this fil­ter­ing to occur at the chan­nel level and not at the pro­cess in­stance. How­ever, that would re­quire the dy­namic cre­ation of chan­nel sub­jects at runtime, one for each pending auc­tion. Presently, there are im­ple­ment­a­tion con­straints within TIB/In­teg­ra­tion­Man­ager that pre­vent us from using this ap­proach. When a pro­cess dia­gram is in­stan­ti­ated at runtime, it needs to re­gister all sub­jects it is listen­ing to. This allows the pro­cess engine to listen for any mes­sages on those sub­jects and queue them as needed. Queuing pre­vents timing bugs due to pro­cess dia­grams that pub­lish on one sub­ject and then trans­ition to an­other state to listen for a reply. In an asyn­chron­ous world, if the trans­ition takes too long, the reply could be re­ceived before the pro­cess dia­gram gets a chance to sub­scribe. There­fore, the pro­cess dia­gram con­veni­ently queues in­bound mes­sages, but at the cost of dis­al­low­ing dy­namic sub­jects. Given the re­quire­ments of the present ex­ample, fil­ter­ing at the pro­cess level works per­fectly well. The Cor­rel­a­tion Iden­ti­fier is a unique iden­ti­fier as­signed to each pro­cess that is then passed to each bank pro­cess and is in­cluded in each reply. A single line of ECMAScript provides the fil­ter­ing in the Signal In task:

                                                                                                                                                                                                (event.msg.CorrelationID == job.bankRequest.CorrelationID)
                                                                                                                                                                                                
                                                                                                                                                                                                (event.msg.Cor­rel­a­tionID == job.bankRe­quest.Cor­rel­a­tionID)
                                                                                                                                                                                                

                                                                                                                                                                                                执行

                                                                                                                                                                                                Ex­e­cu­tion

                                                                                                                                                                                                示例就绪后,我们现在可以运行该解决方案。运行该解决方案涉及启动 TIB/IntegrationManager 引擎。为了测试该解决方案,存储库中包含一个简单的测试客户端,该客户端每 5 秒提交一次贷款请求。简单的存根用于信贷服务和银行的实施。发布订阅通道的优点之一就是我们可以方便地监听消息总线上的所有消息来检查消息流。我们使用一个简单的工具将所有消息记录到控制台。为了清楚起见,我们截断了消息内容以仅显示相关字段,并消除了每条消息中包含的格式标识和跟踪信息。从日志中,我们可以看到消息的时间、主题和收件箱。收件箱是同步服务的返回地址。

                                                                                                                                                                                                With the ex­ample in place, we can now run the solu­tion. Run­ning the solu­tion in­volves start­ing the TIB/In­teg­ra­tion­Man­ager engine. To test the solu­tion, a simple test client is in­cluded in the re­pos­it­ory that sub­mits loan re­quests every 5 seconds. Simple stubs are used for the im­ple­ment­a­tions of the credit ser­vice and the banks. One of the ad­vant­ages of Pub­lish-Sub­scribe Chan­nels is that we can easily listen to all mes­sages on the mes­sage bus to in­spect the mes­sage flow. We used a simple tool to log all mes­sages to the con­sole. For clar­ity, we trun­cated the mes­sage con­tent to show only rel­ev­ant fields and elim­in­ated the format iden­ti­fic­a­tion and track­ing in­form­a­tion in­cluded in each mes­sage. From the logs, we can see the times, sub­jects, and in­boxes of mes­sages. The in­boxes are the return ad­dresses for the syn­chron­ous ser­vices.

                                                                                                                                                                                                
                                                                                                                                                                                                tibrvlisten:听主题 >
                                                                                                                                                                                                
                                                                                                                                                                                                2003-07-12 16:42:30 (2003-07-12 21:42:30.032000000Z):
                                                                                                                                                                                                主题=客户.贷款.请求,
                                                                                                                                                                                                回复=_INBOX.C0A80164.1743F10809898B4B60.3,
                                                                                                                                                                                                SSN=1234567890 贷款金额=100000.000000 贷款期限=360
                                                                                                                                                                                                
                                                                                                                                                                                                2003-07-12 16:42:30 (2003-07-12 21:42:30.052000000Z):
                                                                                                                                                                                                主题=credit.loan.request,
                                                                                                                                                                                                回复=_INBOX.C0A80164.1743F10809898B4B60.4,
                                                                                                                                                                                                SSN=1234567890
                                                                                                                                                                                                
                                                                                                                                                                                                2003-07-12 16:42:30 (2003-07-12 21:42:30.092000000Z):
                                                                                                                                                                                                主题=银行.贷款.请求,
                                                                                                                                                                                                SSN=1234567890 信用评分=345 历史长度=456 贷款金额=100000.000000
                                                                                                                                                                                                CorrelationID="pUQI3GEWK5Q3d-QiuLzzwGM-zzw" 贷款期限=360
                                                                                                                                                                                                
                                                                                                                                                                                                2003-07-12 16:42:30 (2003-07-12 21:42:30.112000000Z):
                                                                                                                                                                                                主题=银行.贷款.回复,
                                                                                                                                                                                                InterestRate=5.017751 QuoteID="5E0x1K_dK5Q3i-QiuMzzwGM-zzw" 错误代码=0
                                                                                                                                                                                                图形/ccc.gifCorrelationID="pUQI3GEWK5Q3d-QiuLzzwGM-zzw"
                                                                                                                                                                                                
                                                                                                                                                                                                2003-07-12 16:42:30 (2003-07-12 21:42:30.112000000Z):
                                                                                                                                                                                                主题=银行.贷款.回复,
                                                                                                                                                                                                InterestRate=5.897514 QuoteID="S9iIAXqgK5Q3n-QiuNzzwGM-zzw" 错误代码=0
                                                                                                                                                                                                图形/ccc.gifCorrelationID="pUQI3GEWK5Q3d-QiuLzzwGM-zzw"
                                                                                                                                                                                                
                                                                                                                                                                                                
                                                                                                                                                                                                tibrvl­isten: Listen­ing to sub­ject >
                                                                                                                                                                                                
                                                                                                                                                                                                2003-07-12 16:42:30 (2003-07-12 21:42:30.032000000Z):
                                                                                                                                                                                                sub­ject=cus­tomer.loan.re­quest,
                                                                                                                                                                                                reply=_INBOX.C0A80164.1743F10809898B4B60.3,
                                                                                                                                                                                                SSN=1234567890 LoanAmount=100000.000000 LoanTerm=360
                                                                                                                                                                                                
                                                                                                                                                                                                2003-07-12 16:42:30 (2003-07-12 21:42:30.052000000Z):
                                                                                                                                                                                                sub­ject=credit.loan.re­quest,
                                                                                                                                                                                                reply=_INBOX.C0A80164.1743F10809898B4B60.4,
                                                                                                                                                                                                SSN=1234567890
                                                                                                                                                                                                
                                                                                                                                                                                                2003-07-12 16:42:30 (2003-07-12 21:42:30.092000000Z):
                                                                                                                                                                                                sub­ject=bank.loan.re­quest,
                                                                                                                                                                                                SSN=1234567890 Cred­itScore=345 His­toryLength=456 LoanAmount=100000.000000
                                                                                                                                                                                                Cor­rel­a­tionID="pUQI3GEWK5Q3d-Qi­uLzzwGM-zzw" LoanTerm=360
                                                                                                                                                                                                
                                                                                                                                                                                                2003-07-12 16:42:30 (2003-07-12 21:42:30.112000000Z):
                                                                                                                                                                                                sub­ject=bank.loan.reply,
                                                                                                                                                                                                In­teres­tRate=5.017751 QuoteID="5E0x1K_dK5Q3i-Qi­uMzzwGM-zzw" Er­ror­Code=0
                                                                                                                                                                                                 Cor­rel­a­tionID="pUQI3GEWK5Q3d-Qi­uLzzwGM-zzw"
                                                                                                                                                                                                
                                                                                                                                                                                                2003-07-12 16:42:30 (2003-07-12 21:42:30.112000000Z):
                                                                                                                                                                                                sub­ject=bank.loan.reply,
                                                                                                                                                                                                In­teres­tRate=5.897514 QuoteID="S9iI­AXqgK5Q3n-Qi­uN­zzwGM-zzw" Er­ror­Code=0
                                                                                                                                                                                                 Cor­rel­a­tionID="pUQI3GEWK5Q3d-Qi­uLzzwGM-zzw"
                                                                                                                                                                                                

                                                                                                                                                                                                我们在通道customer.loan.request 上看到来自测试客户端的请求消息。该消息包括客户的社会安全号码以及所需的贷款金额和期限(360 个月 100,000 美元)。该消息指定测试客户端的私人回复通道_INBOX.C0A80164 。1743F10809898B4B60.3,作为返回地址,以便贷款经纪人知道将回复消息发送到哪里。

                                                                                                                                                                                                We see the re­quest mes­sage from the test client on the chan­nel cus­tomer.loan.re­quest. The mes­sage in­cludes the social se­cur­ity number of the cus­tomer as well as the de­sired loan amount and term ($100,000 for 360 months). The mes­sage spe­cifies the test client's private reply chan­nel, _INBOX.C0A80164. 1743F10809898B4B60.3, as the Return Ad­dress so that the loan broker knows where to send the reply mes­sage.

                                                                                                                                                                                                下一条消息表示从贷款经纪人到信贷服务的请求,携带贷款经纪人的私人收件箱作为返回地址。信用服务只需要社会安全号码。我们的日志记录工具不会捕获回复消息,因为_INBOX频道是私有频道。

                                                                                                                                                                                                The next mes­sage rep­res­ents the re­quest from the loan broker to the credit ser­vice, car­ry­ing the private inbox of the loan broker as the Return Ad­dress. The credit ser­vice re­quires only the social se­cur­ity number. The reply mes­sage is not cap­tured by our log­ging tool be­cause _INBOX chan­nels are private chan­nels.

                                                                                                                                                                                                贷款经纪人收到信用评分后,会向bank.loan.request通道发布一条消息。我们示例中的两家银行存根立即回复各一个利率。每家银行还会为回复分配一个唯一的QuoteID,以便客户稍后可以参考。拍卖时间将在几秒钟内超时。由于我们看不到贷款经纪人进程对测试客户端的回复消息,因此我们可以查看进程管理器引擎的调试日志,以验证我们的测试客户端是否收到了较低的利率。在此消息中,我们可以看到该类CorrelationID和格式指示符。

                                                                                                                                                                                                After the loan broker re­ceives the credit score, it pub­lishes a mes­sage to the bank.loan.re­quest chan­nel. The two bank stubs in our ex­ample im­me­di­ately reply with one in­terest rate each. Each bank also as­signs a unique QuoteID to the reply so that the cus­tomer can refer back to it later. The auc­tion period times out in a few seconds. Be­cause we cannot see the reply mes­sage from the loan broker pro­cess to the test client, we can look in the debug log for the pro­cess man­ager engine to verify that our test client re­ceived the lower in­terest rate. Within this mes­sage we can see the Cor­rel­a­tionID and the Format In­dic­ator for the class.

                                                                                                                                                                                                回复=类/LoanQuoteReply {
                                                                                                                                                                                                    SSN=1234567890
                                                                                                                                                                                                    利率=5.017751017038945
                                                                                                                                                                                                    贷款金额=100000.0
                                                                                                                                                                                                    QuoteID=5E0x1K_dK5Q3i-QiuMzzwGM-zzw
                                                                                                                                                                                                }
                                                                                                                                                                                                
                                                                                                                                                                                                reply= class/Loan­QuoteReply {
                                                                                                                                                                                                    SSN=1234567890
                                                                                                                                                                                                    In­teres­tRate=5.017751017038945
                                                                                                                                                                                                    LoanAmount=100000.0
                                                                                                                                                                                                    QuoteID=5E0x1K_dK5Q3i-Qi­uMzzwGM-zzw
                                                                                                                                                                                                }
                                                                                                                                                                                                

                                                                                                                                                                                                结论

                                                                                                                                                                                                Con­clu­sions

                                                                                                                                                                                                该解决方案提供了一些有趣的比较点。可视化工作流程很容易查看和理解,但就像在代码中一样,随着实现变得更加复杂,这种相对简单的情况可能会变成混乱的迷宫。Scatter-Gather的动态特性允许发布-订阅接口的彻底解耦。贷款经纪人可以在不了解认购银行的情况下发布银行贷款请求。同样,回复聚合器不需要知道预期回复的数量。这种灵活性的潜在缺点是它可能隐藏由于不正确的主题命名、开发人员错误或丢失的消息而导致的错误。

                                                                                                                                                                                                The solu­tion provides some in­ter­est­ing points for com­par­ison. Visual work­flows can be easy to view and un­der­stand, but just as in code, this re­l­at­ive sim­pli­city can turn into a maze of con­fu­sion as im­ple­ment­a­tions become more com­plex. The dy­namic nature of the Scat­ter-Gather allows for a clean de­coup­ling of the pub­lish-sub­scribe in­ter­face. The loan broker can pub­lish bank loan re­quests without any know­ledge of the sub­scrib­ing banks. Sim­il­arly, the Ag­greg­ator for the replies doesn't need to know the number of ex­pec­ted replies. The po­ten­tial down­side of this flex­ib­il­ity is that it can hide errors res­ult­ing from in­cor­rect sub­ject naming, de­ve­loper error, or dropped mes­sages.

                                                                                                                                                                                                TIBCO 的可视化、GUI 驱动的实现为代码级实现提供了一种必然的方法。虽然代码的强大功能和灵活性无可争议,但更加图形化的环境可以极大地简化非常复杂的任务。配置是集成的一个非常重要的部分,图形环境非常适合这一点。有时,开发人员似乎必须在编写代码和使用供应商开发工具之间做出选择。然而,它们通常可以有效共存。例如,您可以选择使用 TIB/IntegrationManager 来建模集成工作流并使用 J2EE 会话 bean 来实现域逻辑。

                                                                                                                                                                                                The visual, GUI-driven im­ple­ment­a­tion with TIBCO provides a co­rol­lary ap­proach to the code-level im­ple­ment­a­tions. While the sheer power and flex­ib­il­ity of code cannot be argued against, a more graph­ical en­vir­on­ment can greatly sim­plify very com­plex tasks. Con­fig­ur­a­tion is a very large part of in­teg­ra­tion, and graph­ical en­vir­on­ments work well for this. At times, it may seem that de­ve­lopers have to choose between writ­ing code and using a vendor de­vel­op­ment tool. How­ever, they can often co­ex­ist ef­fect­ively. For in­stance, you could choose to use TIB/In­teg­ra­tion­Man­ager to model in­teg­ra­tion work­flows and use J2EE ses­sion beans to im­ple­ment domain logic.

                                                                                                                                                                                                为了简洁起见,我们的示例相对简单,导致了一个易于理解的场景。现实世界的实现很少如此简单。快速开发工具可以促进更快的初始开发,但可能会限制您的开发选择。流程管理工具可以帮助隐藏消息传递的复杂性,使开发人员能够专注于集成任务,而不必担心幕后发生的事情。然而,对消息传递基础设施的忽视导致了不止一个项目的消亡。

                                                                                                                                                                                                For the sake of brev­ity, our ex­ample was re­l­at­ively simple, lead­ing to an easy-to-un­der­stand scen­ario. Real-world im­ple­ment­a­tions are rarely this simple. Rapid de­vel­op­ment tools can fa­cil­it­ate faster ini­tial de­vel­op­ment but can limit your de­vel­op­ment op­tions. Pro­cess man­age­ment tools can help hide the com­plex­it­ies of mes­saging, al­low­ing de­ve­lopers to focus on in­teg­ra­tion tasks without wor­ry­ing about what hap­pens behind the scenes. How­ever, ig­nor­ance of mes­saging in­fra­struc­ture has led to the death of more than one pro­ject.

                                                                                                                                                                                                  介绍

                                                                                                                                                                                                  Introduction

                                                                                                                                                                                                  第 3 章“消息系统”中,我们讨论了消息端点。这就是应用程序连接到消息传递系统以便发送和接收消息的方式。作为应用程序程序员,当您对 JMS 或System.Messaging命名空间等消息传递 API 进行编程时,您正在开发端点代码。如果您使用的是商业中间件包,则大部分编码已经通过供应商提供的库和工具为您完成。

                                                                                                                                                                                                  In Chapter 3, "Mes­saging Sys­tems," we dis­cussed Mes­sage En­d­point. This is how an ap­plic­a­tion con­nects to a mes­saging system so that it can send and re­ceive mes­sages. As an ap­plic­a­tion pro­gram­mer, when you pro­gram to a mes­saging API such as JMS or the System.Mes­saging namespace, you're de­vel­op­ing en­d­point code. If you are using a com­mer­cial mid­dle­ware pack­age, most of this coding is already done for you through the lib­rar­ies and tools provided by the vendor.

                                                                                                                                                                                                  发送和接收模式

                                                                                                                                                                                                  Send and Re­ceive Pat­terns

                                                                                                                                                                                                  某些端点模式适用于发送方和接收方。它们关注应用程序与消息传递系统的总体关系。

                                                                                                                                                                                                  Some en­d­point pat­terns apply to both senders and re­ceiv­ers. They con­cern how the ap­plic­a­tion relates to the mes­saging system in gen­eral.

                                                                                                                                                                                                  封装消息传递代码 一般来说,应用程序不应该知道它正在使用消息传递来与其他应用程序集成。大多数应用程序的代码在编写时都应该不考虑消息传递。在应用程序与其他应用程序集成的地方,应该有一个薄层代码来执行应用程序的集成部分。当通过消息传递实现集成时,将应用程序附加到消息传递系统的那层薄代码就是消息传递网关

                                                                                                                                                                                                  数据转换 当​​发送方和接收方应用程序使用相同的内部数据表示形式并且消息格式也使用相同的表示形式时,这会很棒。然而,情况往往并非如此。发送者和接收者在数据格式上不一致,或者消息使用不同的格式(通常是为了支持其他发送者和接收者)。在这种情况下,请使用消息传递映射器在应用程序格式和消息格式之间转换数据。

                                                                                                                                                                                                  外部控制的事务 消息系统在内部使用事务;在外部,默认情况下,每个发送或接收方法调用都在其自己的事务中运行。但是,消息生产者和消费者可以选择使用事务客户端在外部控制这些事务,这在您需要将多个消息批处理在一起或与其他事务服务协调消息传递时非常有用。

                                                                                                                                                                                                  En­cap­su­late the mes­saging code In gen­eral, an ap­plic­a­tion should not be aware that it is using Mes­saging to in­teg­rate with other ap­plic­a­tions. Most of the ap­plic­a­tion's code should be writ­ten without mes­saging in mind. At the points where the ap­plic­a­tion in­teg­rates with others, there should be a thin layer of code that per­forms the ap­plic­a­tion's part of the in­teg­ra­tion. When the in­teg­ra­tion is im­ple­men­ted with mes­saging, that thin layer of code that at­taches the ap­plic­a­tion to the mes­saging system is a Mes­saging Gate­way.

                                                                                                                                                                                                  Data trans­la­tion It's great when the sender and re­ceiver ap­plic­a­tions use the same in­ternal data rep­res­ent­a­tion and when the mes­sage format uses that same rep­res­ent­a­tion as well. How­ever, this is often not the case. Either the sender and re­ceiver dis­agree on data format, or the mes­sages use a dif­fer­ent format (usu­ally to sup­port other senders and re­ceiv­ers). In this situ­ation, use a Mes­saging Mapper to con­vert data between the ap­plic­a­tion's format and the mes­sage's format.

                                                                                                                                                                                                  Ex­tern­ally con­trolled trans­ac­tions Mes­saging sys­tems use trans­ac­tions in­tern­ally; ex­tern­ally, by de­fault, each send or re­ceive method call runs in its own trans­ac­tion. How­ever, mes­sage pro­du­cers and con­sumers have the option of using a Trans­ac­tional Client to con­trol these trans­ac­tions ex­tern­ally, which is useful when you need to batch to­gether mul­tiple mes­sages or to co­ordin­ate mes­saging with other trans­ac­tional ser­vices.

                                                                                                                                                                                                  消息消费者模式

                                                                                                                                                                                                  Mes­sage Con­sumer Pat­terns

                                                                                                                                                                                                  其他端点模式仅适用于消息使用者。发送消息很容易。在决定何时发送消息、消息应包含什么内容以及如何将其意图传达给接收者时,会涉及到一些问题,这就是我们使用消息构造模式的原因(请参阅第 5 章“消息构造”),但是消息一旦构建,发送很容易。另一方面,接收消息则很棘手。因此,许多端点模式都是关于接收消息的。

                                                                                                                                                                                                  Other en­d­point pat­terns apply only to mes­sage con­sumers. Send­ing mes­sages is easy. There are issues in­volved in de­cid­ing when a mes­sage should be sent, what it should con­tain, and how to com­mu­nic­ate its intent to the re­ceiv­er­that's why we have the Mes­sage Con­struc­tion pat­terns (see Chapter 5, "Mes­sage Con­struc­tion")but once the mes­sage is built, send­ing it is easy. Re­ceiv­ing mes­sages, on the other handthat's tricky. There­fore, many en­d­point pat­terns are about re­ceiv­ing mes­sages.

                                                                                                                                                                                                  消息消费中最重要的主题是限制:应用程序控制或限制其消费消息的速率的能力。正如本书的简介中所讨论的,任何服务器面临的一个潜在问题是大量的客户端请求可能会使服务器过载。使用远程过程调用,服务器几乎受客户端调用速率的影响。同样,对于消息传递,服务器无法控制客户端发送请求的速率,但服务器可以控制处理这些请求的速率。应用程序不必像消息传递系统传送消息那样快地接收和处理消息;它可以以可持续的速度处理它们,而消息通道以先到先服务的方式对要处理的消息进行排队。但是,如果消息堆积太多并且服务器有资源更快地处理更多消息,则服务器可以使用并发消息使用者来增加其消息消费吞吐量。因此,使用这些消息消费者模式可以让您的应用程序控制其消费消息的速率。

                                                                                                                                                                                                  An over­rid­ing theme in mes­sage con­sump­tion is throt­tling: the abil­ity of an ap­plic­a­tion to con­trol, or throttle, the rate at which it con­sumes mes­sages. As dis­cussed in the book's In­tro­duc­tion, a po­ten­tial prob­lem any server faces is that a high volume of client re­quests could over­load the server. With Remote Pro­ced­ure In­voc­a­tion, the server is pretty much at the mercy of the rate that cli­ents make calls. Like­wise, with Mes­saging, the server cannot con­trol the rate at which cli­ents send re­quests­but the server can con­trol the rate at which it pro­cesses those re­quests. The ap­plic­a­tion does not have to re­ceive and pro­cess the mes­sages as rap­idly as they're de­livered by the mes­saging system; it can pro­cess them at a sus­tain­able pace while the Mes­sage Chan­nel queues up the mes­sages to be pro­cessed in a first come, first served basis. How­ever, if the mes­sages are piling up too much and the server has the re­sources to pro­cess more mes­sages faster, the server can in­crease its mes­sage con­sump­tion through­put using con­cur­rent mes­sage con­sumers. So use these mes­sage con­sumer pat­terns to let your ap­plic­a­tion con­trol the rate at which it con­sumes mes­sages.

                                                                                                                                                                                                  许多消息使用者模式成对出现,代表替代方案,这意味着您可以以一种或另一种方式设计端点。单个应用程序可以以一种方式设计某些端点,以另一种方式设计某些端点,但单个端点只能实现一种替代方案。每一对的替代方案可以组合起来,从而为如何实现特定端点提供大量选择。

                                                                                                                                                                                                  Many of the mes­sage con­sumer pat­terns come in pairs that rep­res­ent al­tern­at­ives, mean­ing that you can design an en­d­point one way or the other. A single ap­plic­a­tion may design some en­d­points one way and some en­d­points the other way, but a single en­d­point can im­ple­ment only one al­tern­at­ive. Al­tern­at­ives from each pair can be com­bined, lead­ing to a great number of choices for how to im­ple­ment a par­tic­u­lar en­d­point.

                                                                                                                                                                                                  同步或异步消费者 一种选择是使用轮询消费者还是事件驱动消费者 [JMS 1.1 ]、[ Hapner ]、[ Dickman ] 。轮询提供了最好的限制,因为如果服务器繁忙,它不会请求更多消息,因此消息将排队。事件驱动的消费者倾向于在消息到达时尽快处理消息,这可能会使服务器过载;但每个消费者一次只能处理一条消息,因此限制消费者的数量可以有效地限制消费速度。

                                                                                                                                                                                                  消息分配与消息抓取 另一种选择涉及少数消费者如何处理少数消息。如果每个消费者收到一条消息,他们可以同时处理这些消息。最简单的方法是竞争消费者,其中一个点对点通道有多个消费者。每个人都可能抓取任何消息;消息系统的实现决定了哪个消费者接收消息。如果您想控制此消息到消费者的匹配过程,请使用消息调度程序。这是一个接收消息但将其委托给执行者进行处理的单个消费者。应用程序可以通过限制消费者/执行者的数量来限制消息负载。此外,消息调度程序中的调度程序可以实现显式限制行为。

                                                                                                                                                                                                  接受所有消息或过滤器 默认情况下,消息通道上传递的任何消息都可供侦听该通道的任何消息端点使用以供消息使用。然而,一些消费者可能不想只消费该通道上的任何消息,而是希望只消费某种类型或描述的消息。这种有区别的消费者可以使用选择性消费者来描述它愿意接收哪种消息。然后,消息传递系统将仅向该接收者提供与该描述匹配的消息。

                                                                                                                                                                                                  断开连接时订阅 发布-订阅通道出现的一个 问题是,如果订阅者对在特定通道上发布的数据感兴趣并且将再次感兴趣,但当前与网络断开连接或关闭以进行维护,该怎么办?断开连接的应用程序是否会错过断开连接时发布的消息,即使它已订阅?默认情况下,是的,订阅仅在订阅者连接时有效。为了防止应用程序丢失连接之间发布的消息,请将其设为持久订阅者

                                                                                                                                                                                                  幂等性 有时,同一条消息会被多次传递,要么是因为消息传递系统不确定消息是否已成功传递,要么是因为消息通道的服务质量已降低以提高性能。另一方面,消息接收者倾向于假设每条消息只会被传递一次,并且在由于重复消息而重复处理时往往会导致问题。设计为幂等接收器的接收器可以优雅地处理重复消息,并防止它们在接收器应用程序中引起问题。

                                                                                                                                                                                                  同步或异步服务 另一个艰难的选择是应用程序是否应该公开其服务以同步(通过远程过程调用)或异步(通过消息传递)调用。不同的客户可能喜欢不同的方法;不同的情况可能需要不同的方法。由于通常很难选择一种方法或另一种方法,所以让我们同时采用两种方法。服务激活器连接消息通道到应用程序中的同步服务,以便在收到消息时调用该服务。同步客户端可以直接调用服务;异步客户端可以通过发送消息来调用服务。

                                                                                                                                                                                                  Syn­chron­ous or asyn­chron­ous con­sumer One al­tern­at­ive is whether to use a Polling Con­sumer or an Event-Driven Con­sumer [JMS 1.1], [Hapner], [Dick­man]. Polling provides the best throt­tling be­cause if the server is busy, it won't ask for more mes­sages, so the mes­sages will queue up. Con­sumers that are event-driven tend to pro­cess mes­sages as fast as they arrive, which could over­load the server; but each con­sumer can only pro­cess one mes­sage at a time, so lim­it­ing the number of con­sumers ef­fect­ively throttles the con­sump­tion rate.

                                                                                                                                                                                                  Mes­sage as­sign­ment versus mes­sage grab An­other al­tern­at­ive con­cerns how a hand­ful of con­sumers pro­cess a hand­ful of mes­sages. If each con­sumer gets a mes­sage, they can pro­cess the mes­sages con­cur­rently. The simplest ap­proach is Com­pet­ing Con­sumers, where one Point-to-Point Chan­nel has mul­tiple con­sumers. Each one could po­ten­tially grab any mes­sage; the mes­saging system's im­ple­ment­a­tion de­cides which con­sumer gets a mes­sage. If you want to con­trol this mes­sage-to-con­sumer match­ing pro­cess, use a Mes­sage Dis­patcher. This is a single con­sumer that re­ceives a mes­sage but del­eg­ates it to a per­former for pro­cess­ing. An ap­plic­a­tion can throttle mes­sage load by lim­it­ing the number of con­sumers/per­formers. Also, the dis­patcher in a Mes­sage Dis­patcher can im­ple­ment ex­pli­cit throt­tling be­ha­vior.

                                                                                                                                                                                                  Accept all mes­sages or filter By de­fault, any mes­sage de­livered on a Mes­sage Chan­nel be­comes avail­able to any Mes­sage En­d­point listen­ing on that chan­nel for mes­sages to con­sume. How­ever, some con­sumers may not want to con­sume just any mes­sage on that chan­nel, but wish to con­sume only mes­sages of a cer­tain type or de­scrip­tion. Such a dis­crim­in­at­ing con­sumer can use a Se­lect­ive Con­sumer to de­scribe what sort of mes­sage it's will­ing to re­ceive. Then the mes­saging system will make only mes­sages match­ing that de­scrip­tion avail­able to that re­ceiver.

                                                                                                                                                                                                  Sub­scribe while dis­con­nec­ted An issue that comes up with Pub­lish-Sub­scribe Chan­nels is, What if a sub­scriber was in­ter­ested in the data being pub­lished on a par­tic­u­lar chan­nel and will be again, but is cur­rently dis­con­nec­ted from the net­work or shut down for main­ten­ance? Will a dis­con­nec­ted ap­plic­a­tion miss mes­sages pub­lished while it is dis­con­nec­ted, even though it has sub­scribed? By de­fault, yes, a sub­scrip­tion is only valid while the sub­scriber is con­nec­ted. To keep the ap­plic­a­tion from miss­ing mes­sages pub­lished between con­nec­tions, make it a Dur­able Sub­scriber.

                                                                                                                                                                                                  Idem­po­tency Some­times the same mes­sage gets de­livered more than once, either be­cause the mes­saging system is not cer­tain the mes­sage has been suc­cess­fully de­livered yet, or be­cause the Mes­sage Chan­nel's qual­ity-of-ser­vice has been lowered to im­prove per­form­ance. Mes­sage re­ceiv­ers, on the other hand, tend to assume that each mes­sage will be de­livered ex­actly once, and they tend to cause prob­lems when they repeat pro­cess­ing be­cause of repeat mes­sages. A re­ceiver de­signed as an Idem­potent Re­ceiver handles du­plic­ate mes­sages grace­fully and pre­vents them from caus­ing prob­lems in the re­ceiver ap­plic­a­tion.

                                                                                                                                                                                                  Syn­chron­ous or asyn­chron­ous ser­vice An­other tough choice is whether an ap­plic­a­tion should expose its ser­vices to be in­voked syn­chron­ously (via Remote Pro­ced­ure In­voc­a­tion ) or asyn­chron­ously (via Mes­saging ). Dif­fer­ent cli­ents may prefer dif­fer­ent ap­proaches; dif­fer­ent cir­cum­stances may re­quire dif­fer­ent ap­proaches. Since it's often hard to choose just one ap­proach or the other, let's have both. A Ser­vice Ac­tiv­ator con­nects a Mes­sage Chan­nel to a syn­chron­ous ser­vice in an ap­plic­a­tion so that when a mes­sage is re­ceived, the ser­vice is in­voked. Syn­chron­ous cli­ents can simply invoke the ser­vice dir­ectly; asyn­chron­ous cli­ents can invoke the ser­vice by send­ing a mes­sage.

                                                                                                                                                                                                  消息端点主题

                                                                                                                                                                                                  Mes­sage En­d­point Themes

                                                                                                                                                                                                  本章的另一个重要主题是将事务客户端与其他模式一起使用的困难。事件驱动的消费者通常无法从外部正确控制事务,消息调度程序必须仔细设计才能做到这一点,并且外部管理事务的竞争消费者可能会遇到严重的问题。使用Transactional Client 的最安全选择是使用单个Polling Consumer ,但这可能不是一个非常令人满意的解决方案。

                                                                                                                                                                                                  An­other sig­ni­fic­ant theme in this chapter is dif­fi­culty using Trans­ac­tional Client with other pat­terns. Event-Driven Con­sumer usu­ally cannot ex­tern­ally con­trol trans­ac­tions prop­erly, Mes­sage Dis­patcher must be care­fully de­signed to do so, and Com­pet­ing Con­sumers that ex­tern­ally manage trans­ac­tions can run into sig­ni­fic­ant prob­lems. The safest bet for using Trans­ac­tional Client is with a single Polling Con­sumer, but that may not be a very sat­is­fact­ory solu­tion.

                                                                                                                                                                                                  特别值得一提的是 JMS 风格的消息驱动 Bean (MDB),它是 Enterprise JavaBean (EJB) 的一种类型 [ EJB 2.0 ]、[ Hapner ]。MDB 是一个消息使用者,它既是事件驱动使用者又是支持 J2EE 分布式(例如XAResource )事务客户端,并且它可以动态地汇集为竞争使用者,甚至对于发布-订阅通道也是如此。。在自己的应用程序代码中实现这种组合既困难又乏味,但此功能是作为兼容 EJB 容器(例如 BEA 的 WebLogic 和 IBM 的 WebSphere)的现成功能提供的。(MDB 框架是如何实现的?本质上,容器实现了一个具有动态大小的可重用执行者池的消息调度程序,其中每个执行者使用自己的会话和事务来消费消息本身。)

                                                                                                                                                                                                  Spe­cial men­tion should be made of JMS-style mes­sage-driven beans (MDBs), one type of En­ter­prise Java­Beans (EJB) [EJB 2.0], [Hapner]. An MDB is a mes­sage con­sumer that is both an Event-Driven Con­sumer and a Trans­ac­tional Client that sup­ports J2EE dis­trib­uted (e.g., XARe­source) trans­ac­tions, and it can be dy­nam­ic­ally pooled as Com­pet­ing Con­sumers, even for a Pub­lish-Sub­scribe Chan­nel. This is a dif­fi­cult and te­di­ous com­bin­a­tion to im­ple­ment in one's own ap­plic­a­tion code, but this func­tion­al­ity is provided as a ready-built fea­ture of com­pat­ible EJB con­tain­ers (such as BEA's Web­Lo­gic and IBM's Web­Sphere). (How is the MDB frame­work im­ple­men­ted? Es­sen­tially, the con­tainer im­ple­ments a Mes­sage Dis­patcher with a dy­nam­ic­ally sized pool of re­usable per­formers, where each per­former con­sumes the mes­sage itself using its own ses­sion and trans­ac­tion.)

                                                                                                                                                                                                  最后,请记住,单个消息端点很可能组合本章中的几种不同模式。一组竞争消费者可以被实现为轮询消费者,它们也是选择性消费者并充当应用程序中服务的服务激活器。消息调度程序可以是事件驱动的消费者和使用消息映射器的持久订阅者。无论端点实现什么其他模式,它也应该是一个消息传递网关。因此,不要考虑使用哪一种模式,而是考虑组合。这就是用模式解决问题的美妙之处。

                                                                                                                                                                                                  Fi­nally, keep in mind that a single Mes­sage En­d­point may well com­bine sev­eral dif­fer­ent pat­terns from this chapter. A group of Com­pet­ing Con­sumers may be im­ple­men­ted as Polling Con­sumers that are also Se­lect­ive Con­sumers and act as a Ser­vice Ac­tiv­ator on a ser­vice in the ap­plic­a­tion. A Mes­sage Dis­patcher may be an Event-Driven Con­sumer and a Dur­able Sub­scriber that uses a Mes­saging Mapper. Whatever other pat­terns an en­d­point im­ple­ments, it should also be a Mes­saging Gate­way. So, don't think of what one pat­tern to use­think of the com­bin­a­tions. That's the beauty of solv­ing the prob­lems with pat­terns.

                                                                                                                                                                                                  有很多选项可以使应用程序成为消息端点。本章解释了这些选项是什么以及如何充分利用它们。

                                                                                                                                                                                                  There are a lot of op­tions for making an ap­plic­a­tion into a Mes­sage En­d­point. This chapter ex­plains what those op­tions are and how to make the best use of them.

                                                                                                                                                                                                    消息传送网关

                                                                                                                                                                                                    Messaging Gateway

                                                                                                                                                                                                    图形/messaginggateway_icon.gif

                                                                                                                                                                                                    应用程序通过消息传递访问另一个系统

                                                                                                                                                                                                    An ap­plic­a­tion ac­cesses an­other system via Mes­saging.

                                                                                                                                                                                                    如何封装应用程序其余部分对消息传递系统的访问?

                                                                                                                                                                                                    How do you en­cap­su­late access to the mes­saging system from the rest of the ap­plic­a­tion?



                                                                                                                                                                                                    大多数自定义应用程序通过供应商提供的 API 访问消息传递基础设施。虽然此类 API 有许多不同的风格,但这些库通常公开类似的功能,例如“开放通道”、“创建消息”和“发送消息”。虽然这种类型的 API 允许应用程序通过任何类型的通道发送任何类型的消息数据,但有时很难判断发送消息数据的意图是什么。

                                                                                                                                                                                                    Most custom ap­plic­a­tions access the mes­saging in­fra­struc­tures through a vendor-sup­plied API. While there are many dif­fer­ent fla­vors of such APIs, these lib­rar­ies gen­er­ally expose sim­ilar func­tions, such as "open chan­nel," "create mes­sage," and "send mes­sage." While this type of API allows the ap­plic­a­tion to send any kind of mes­sage data across any kind of chan­nel, it is some­times hard to tell what the intent of send­ing the mes­sage data is.

                                                                                                                                                                                                    消息传递解决方案本质上是异步的。这可能会使通过消息传递访问外部函数的代码变得复杂。应用程序必须发送请求消息并期望回复消息稍后到达(请参阅Request-Reply ),而不是调用返回数字信用评分的方法GetCreditScore。应用程序开发人员可能更喜欢同步函数的简单语义,而不是处理传入的消息事件。

                                                                                                                                                                                                    Mes­saging solu­tions are in­her­ently asyn­chron­ous. This can com­plic­ate the code to access an ex­ternal func­tion over mes­saging. In­stead of call­ing a method GetCred­itScore that re­turns the nu­meric credit score, the ap­plic­a­tion has to send the re­quest mes­sage and expect the reply mes­sage to arrive at a later time (see Re­quest-Reply ). The ap­plic­a­tion de­ve­loper may prefer the simple se­mantics of a syn­chron­ous func­tion to deal­ing with in­com­ing mes­sage events.

                                                                                                                                                                                                    应用程序之间的松散耦合提供了体系结构优势,例如对消息格式的微小变化(即添加字段)的恢复能力。通常,松耦合是通过使用 XML 文档或其他不像 Java 或 C# 类那样强类型的数据结构来实现的。针对此类结构进行编码既乏味又容易出错,因为没有编译类型支持来检测拼写错误的字段名称或不匹配的数据类型。因此,我们常常以牺牲应用程序开发工作量为代价来获得数据格式的灵活性。

                                                                                                                                                                                                    Loose coup­ling between ap­plic­a­tions provides ar­chi­tec­tural ad­vant­ages, such as re­si­li­ence to minor changes in mes­sage formats (i.e., adding fields). Usu­ally, the loose coup­ling is achieved by using XML doc­u­ments or other data struc­tures that are not strongly typed like a Java or C# class. Coding against such struc­tures is te­di­ous and error-prone be­cause there is no com­pile-type sup­port to detect mis­spelled field names or mis­matched data­types. There­fore, we often gain the flex­ib­il­ity in data formats at the ex­pense of ap­plic­a­tion de­vel­op­ment effort.

                                                                                                                                                                                                    有时,通过消息传递执行的简单逻辑功能需要发送多个消息。例如,获取客户信息的功能实际上可能需要多条消息,一条消息用于获取地址,另一条消息用于获取订单历史记录,另一条消息用于获取个人信息。这些消息中的每一个都可以由不同的系统处理。我们不想让应用程序代码与发送和接收三个单独消息所需的所有逻辑混淆。我们可以通过使用Scatter-Gather来减轻应用程序的一些负担它接收一条消息,发送三个单独的消息,并将它们聚合回一条回复消息。然而,我们并不总是有能力将此功能添加到消息传递中间件中。

                                                                                                                                                                                                    Some­times, a simple lo­gical func­tion to be ex­ecuted via mes­saging re­quires more than one mes­sage to be sent. For ex­ample, a func­tion to get cus­tomer in­form­a­tion may in real­ity re­quire mul­tiple mes­sages, one to get the ad­dress, an­other to get the order his­tory, and yet an­other to get per­sonal in­form­a­tion. Each of these mes­sages may be pro­cessed by a dif­fer­ent system. We would not want to clut­ter the ap­plic­a­tion code with all the logic re­quired to send and re­ceive three sep­ar­ate mes­sages. We could take some of the burden off the ap­plic­a­tion by using a Scat­ter-Gather that re­ceives a single mes­sage, sends three sep­ar­ate mes­sages, and ag­greg­ates them back into a single reply mes­sage. How­ever, not always do we have the luxury of adding this func­tion to the mes­saging mid­dle­ware.

                                                                                                                                                                                                    使用消息传递网关,这是一个包装特定于消息传递的方法调用并向应用程序公开特定于域的方法的类。

                                                                                                                                                                                                    Use a Mes­saging Gate­way, a class that wraps mes­saging-spe­cific method calls and ex­poses domain-spe­cific meth­ods to the ap­plic­a­tion.

                                                                                                                                                                                                    图形/10inf01.gif



                                                                                                                                                                                                    消息传送网关封装消息传送特定的代码(例如,发送或接收消息所需的代码)并将其与应用程序代码的其余部分分开。这样,只有消息网关代码知道消息系统;其余的应用程序代码则不然。消息传递网关向应用程序的其余部分公开业务功能,以便不需要应用程序设置诸如Message.MessageReadPropertyFilter.AppSpecific 之类的属性,消息传递网关公开有意义的方法(例如GetCreditScore ,这些方法像任何其他方法一样接受强类型参数。消息传递网关是更通用的网关模式[ EAA]的消息传递特定版本。

                                                                                                                                                                                                    The Mes­saging Gate­way en­cap­su­lates mes­saging-spe­cific code (e.g., the code re­quired to send or re­ceive a mes­sage) and sep­ar­ates it from the rest of the ap­plic­a­tion code. This way, only the Mes­saging Gate­way code knows about the mes­saging system; the rest of the ap­plic­a­tion code does not. The Mes­saging Gate­way ex­poses a busi­ness func­tion to the rest of the ap­plic­a­tion so that in­stead of re­quir­ing the ap­plic­a­tion to set prop­er­ties like Mes­sage.Mes­sageRead­Prop­er­ty­Fil­ter.AppSpe­cific, a Mes­saging Gate­way ex­poses mean­ing­ful meth­ods such as GetCred­itScore that accept strongly typed para­met­ers just like any other method. A Mes­saging Gate­way is a mes­saging-spe­cific ver­sion of the more gen­eral Gate­way pat­tern [EAA].

                                                                                                                                                                                                    网关消除了应用程序和消息系统之间的直接依赖性

                                                                                                                                                                                                    A Gate­way Elim­in­ates Direct De­pend­en­cies between the Ap­plic­a­tion and the Mes­saging Sys­tems

                                                                                                                                                                                                    图形/10inf02.gif

                                                                                                                                                                                                    消息传递网关位于应用程序和消息传递系统之间,并向应用程序提供特定于域的 API(请参见上图)。因为应用程序甚至不知道它正在使用消息传递系统,所以我们可以将网关替换为使用另一种集成技术(例如远程过程调用或 Web 服务)的不同实现。

                                                                                                                                                                                                    A Mes­saging Gate­way sits between the ap­plic­a­tion and the mes­saging system and provides a domain-spe­cific API to the ap­plic­a­tion (see pre­vi­ous figure). Be­cause the ap­plic­a­tion doesn't even know that it's using a mes­saging system, we can swap out the gate­way with a dif­fer­ent im­ple­ment­a­tion that uses an­other in­teg­ra­tion tech­no­logy, such as remote pro­ced­ure calls or Web ser­vices.

                                                                                                                                                                                                    许多消息网关将消息发送到另一个组件并期望回复消息(请参阅请求-回复) 。这样的消息传递网关可以通过两种不同的方式实现:

                                                                                                                                                                                                    Many Mes­saging Gate­ways send a mes­sage to an­other com­pon­ent and expect a reply mes­sage (see Re­quest-Reply ). Such a Mes­saging Gate­way can be im­ple­men­ted in two dif­fer­ent ways:

                                                                                                                                                                                                    1. 阻塞(同步)消息传递网关

                                                                                                                                                                                                    2. Block­ing (Syn­chron­ous) Mes­saging Gate­way

                                                                                                                                                                                                    3. 事件驱动(异步)消息传递网关

                                                                                                                                                                                                    4. Event-Driven (Asyn­chron­ous) Mes­saging Gate­way

                                                                                                                                                                                                    阻塞消息网关发送消息并等待回复消息到达,然后将控制权返回给应用程序。当网关收到回复时,它会处理消息并将结果返回给应用程序(请参见以下序列图)。

                                                                                                                                                                                                    A block­ing Mes­saging Gate­way sends out a mes­sage and waits for the reply mes­sage to arrive before re­turn­ing con­trol to the ap­plic­a­tion. When the gate­way re­ceives the reply, it pro­cesses the mes­sage and re­turns the result to the ap­plic­a­tion (see the fol­low­ing se­quence dia­gram).

                                                                                                                                                                                                    阻塞(同步)消息传递网关

                                                                                                                                                                                                    Block­ing (Syn­chron­ous) Mes­saging Gate­way

                                                                                                                                                                                                    图形/10inf03.gif

                                                                                                                                                                                                    阻塞消息传递网关封装了消息传递交互的异步性质,向应用程序逻辑公开常规同步方法。因此,应用程序不知道通信中的任何异步性。例如,阻塞网关可能会公开以下方法:

                                                                                                                                                                                                    A block­ing Mes­saging Gate­way en­cap­su­lates the asyn­chron­ous nature of the mes­saging in­ter­ac­tion, ex­pos­ing a reg­u­lar syn­chron­ous method to the ap­plic­a­tion logic. Thus, the ap­plic­a­tion is un­aware of any asyn­chron­icity in the com­mu­nic­a­tion. For ex­ample, a block­ing gate­way may expose the fol­low­ing method:

                                                                                                                                                                                                    int GetCreditScore(字符串 SSN);
                                                                                                                                                                                                    
                                                                                                                                                                                                    int GetCred­itScore(string SSN);
                                                                                                                                                                                                    

                                                                                                                                                                                                    虽然这种方法使得针对消息网关编写应用程序代码变得非常简单,但它也可能导致性能不佳,因为应用程序最终将大部分时间花在等待回复消息上,而它可能正在执行其他任务。

                                                                                                                                                                                                    While this ap­proach makes writ­ing ap­plic­a­tion code against the Mes­saging Gate­way very simple, it can also lead to poor per­form­ance be­cause the ap­plic­a­tion ends up spend­ing most of its time sit­ting around and wait­ing for reply mes­sages while it could be per­form­ing other tasks.

                                                                                                                                                                                                    事件驱动的消息传递网关向应用程序公开消息传递层的异步特性。当应用程序向消息传递网关发出特定于域的请求时它会提供特定于域的回调来进行回复。控制立即返回到应用程序。当回复消息到达时,消息网关对其进行处理,然后调用回调(请参见以下序列图)。

                                                                                                                                                                                                    An event-driven Mes­saging Gate­way ex­poses the asyn­chron­ous nature of the mes­saging layer to the ap­plic­a­tion. When the ap­plic­a­tion makes the domain-spe­cific re­quest to the Mes­saging Gate­way, it provides a domain-spe­cific call­back for the reply. Con­trol re­turns im­me­di­ately to the ap­plic­a­tion. When the reply mes­sage ar­rives, the Mes­saging Gate­way pro­cesses it and then in­vokes the call­back (see the fol­low­ing se­quence dia­gram).

                                                                                                                                                                                                    事件驱动(异步)消息传递网关

                                                                                                                                                                                                    Event-Driven (Asyn­chron­ous) Mes­saging Gate­way

                                                                                                                                                                                                    图形/10inf04.gif

                                                                                                                                                                                                    例如,在 C# 中,使用委托,消息传递网关可以公开以下公共接口:

                                                                                                                                                                                                    For ex­ample, in C#, using del­eg­ates, the Mes­saging Gate­way could expose the fol­low­ing public in­ter­face:

                                                                                                                                                                                                    delegate void OnCreditReplyEvent(int CreditScore);
                                                                                                                                                                                                    void RequestCreditScore(string SSN, OnCreditReplyEvent OnCreditResponse);
                                                                                                                                                                                                    
                                                                                                                                                                                                    del­eg­ate void On­CreditReplyEvent(int Cred­itScore);
                                                                                                                                                                                                    void Re­quest­Cred­itScore(string SSN, On­CreditReplyEvent On­CreditRe­sponse);
                                                                                                                                                                                                    

                                                                                                                                                                                                    RequestCreditScore方法接受一个附加参数,该参数指定回复消息到达时要调用的回调方法。回调方法有一个参数CreditScore,以便消息网关可以将结果传递给应用程序。根据编程语言或平台,回调可以通过函数指针、对象引用或委托来完成(如此处所示)。请注意,尽管此接口具有事件驱动的性质,但根本不依赖于特定的消息传递技术。

                                                                                                                                                                                                    The method Re­quest­Cred­itScore ac­cepts an ad­di­tional para­meter that spe­cifies the call­back method to be in­voked when the reply mes­sage ar­rives. The call­back method has a para­meter Cred­itScore so that the Mes­saging Gate­way can pass the res­ults to the ap­plic­a­tion. De­pend­ing on the pro­gram­ming lan­guage or plat­form, the call­back can be ac­com­plished with func­tion point­ers, object ref­er­ences, or del­eg­ates (as shown here). Note that des­pite the event-driven nature of this in­ter­face, there is no de­pend­ency at all on a spe­cific mes­saging tech­no­logy.

                                                                                                                                                                                                    或者,应用程序可以定期轮询以查看结果是否到达。这种方法使高层接口变得简单,且不会引入阻塞,本质上是采用半同步/半异步模式 [ POSA2 ]。此模式描述了使用存储传入消息的缓冲区,以便应用程序可以在方便时轮询以查看消息是否已到达。

                                                                                                                                                                                                    Al­tern­at­ively, the ap­plic­a­tion can peri­od­ic­ally poll to see whether the res­ults ar­rived. This ap­proach makes the higher-level in­ter­face simple without in­tro­du­cing block­ing, es­sen­tially em­ploy­ing the Half-Sync/Half-Async pat­tern [POSA2]. This pat­tern de­scribes the use of buf­fers that store in­com­ing mes­sages so that the ap­plic­a­tion can poll at its con­veni­ence to see whether a mes­sage has ar­rived.

                                                                                                                                                                                                    使用事件驱动的消息传递网关的挑战之一是消息传递网关要求应用程序维护请求方法和回调事件之间的状态(调用堆栈在阻塞情况下负责处理此问题)。当消息传递网关将回调事件调用到应用程序逻辑中时,应用程序必须能够将回复与之前发出的请求关联起来,以便它可以继续处理正确的执行线程。如果消息传递网关允许应用程序将对任意数据集的引用传递给请求方法,那么它可以使应用程序更轻松地维护状态。消息传送网关然后将通过回调将此数据传递回应用程序。这样,当调用异步回调时,应用程序就可以获得所有必需的数据。这种类型的交互通常称为 ACT(异步完成令牌)[ POSA2 ]。

                                                                                                                                                                                                    One of the chal­lenges of using an event-driven Mes­saging Gate­way is that the Mes­saging Gate­way re­quires the ap­plic­a­tion to main­tain state between the re­quest method and the call­back event (the call stack takes care of this in the block­ing case). When the Mes­saging Gate­way in­vokes the call­back event into the ap­plic­a­tion logic, the ap­plic­a­tion must be able to cor­rel­ate the reply with the re­quest it made earlier so that it can con­tinue pro­cess­ing the cor­rect thread of ex­e­cu­tion. The Mes­saging Gate­way can make it easier for the ap­plic­a­tion to main­tain state if it allows the ap­plic­a­tion to pass a ref­er­ence to an ar­bit­rary set of data to the re­quest method. The Mes­saging Gate­way will then pass this data back to the ap­plic­a­tion with the call­back. This way, the ap­plic­a­tion has all ne­ces­sary data avail­able when the asyn­chron­ous call­back is in­voked. This type of in­ter­ac­tion is com­monly called ACT (Asyn­chron­ous Com­ple­tion Token) [POSA2].

                                                                                                                                                                                                    支持 ACT 的事件驱动消息传递网关的公共接口可能如下所示:

                                                                                                                                                                                                    The public in­ter­face of an event-driven Mes­saging Gate­way that sup­ports an ACT may look like this:

                                                                                                                                                                                                    delegate void OnCreditReplyEvent(int CreditScore, Object ACT);
                                                                                                                                                                                                    void RequestCreditScore(string SSN, OnCreditReplyEvent OnCreditResponse, 对象 ACT);
                                                                                                                                                                                                    
                                                                                                                                                                                                    del­eg­ate void On­CreditReplyEvent(int Cred­itScore, Object ACT);
                                                                                                                                                                                                    void Re­quest­Cred­itScore(string SSN, On­CreditReplyEvent On­CreditRe­sponse, Object ACT);
                                                                                                                                                                                                    

                                                                                                                                                                                                    RequestCreditScore方法有一个附加参数,即对通用对象的引用。消息网关存储该引用,等待回复消息到达。当回复到达时,网关调用OnCreditReplyEvent类型的委托,传入操作结果以及对象引用。虽然支持 ACT 对于应用程序来说是一个非常方便的功能,但如果消息传递网关维护对对象的引用但预期的回复消息永远不会到达,它确实会带来内存泄漏的危险。

                                                                                                                                                                                                    The method Re­quest­Cred­itScore fea­tures an ad­di­tional para­meter, a ref­er­ence to a gen­eric object. The Mes­saging Gate­way stores this ref­er­ence, wait­ing for the reply mes­sage to arrive. When the reply ar­rives, the gate­way in­vokes the del­eg­ate of type On­CreditReplyEvent, passing in the result of the op­er­a­tion as well as the object ref­er­ence. While sup­port­ing an ACT is a very con­veni­ent fea­ture for the ap­plic­a­tion, it does in­tro­duce the danger of a memory leak if the Mes­saging Gate­way main­tains a ref­er­ence to an object but the ex­pec­ted reply mes­sage never ar­rives.

                                                                                                                                                                                                    链接网关

                                                                                                                                                                                                    Chain­ing Gate­ways

                                                                                                                                                                                                    创建多于一层的消息传递网关可能是有益的。“较低级别”消息传递网关可以简单地抽象消息传递系统的语法,但维护通用消息传递语义,例如SendMessage 。当企业更改消息传递技术(例如从 MSMQ 到 Web 服务)时,此消息传递网关可以帮助屏蔽应用程序的其余部分。我们用一个附加的消息传递网关包装这个基本的消息传递网关,该网关将通用消息传递 API 转换为狭窄的、特定于域的 API,例如GetCreditScore我们在 Loan Broker 示例的 MSMQ 实现中使用此配置(参见下图;另请参见第 9 章“插曲:组合消息传递”中的“ MSMQ 异步实现”部分)。

                                                                                                                                                                                                    It can be be­ne­fi­cial to create more than one layer of Mes­saging Gate­ways. The "lower-level" Mes­saging Gate­way can simply ab­stract the syntax of the mes­saging system but main­tain gen­eric mes­saging se­mantics, for ex­ample, SendMes­sage. This Mes­saging Gate­way can help shield the rest of the ap­plic­a­tion when the en­ter­prise changes mes­saging tech­no­lo­gies, for ex­ample, from MSMQ to Web ser­vices. We wrap this basic Mes­saging Gate­way with an ad­di­tional Mes­saging Gate­way that trans­lates the gen­eric mes­saging API into a narrow, domain-spe­cific API, such as GetCred­itScore. We use this con­fig­ur­a­tion in the MSMQ im­ple­ment­a­tion of the Loan Broker ex­ample (see the fol­low­ing figure; also see the sec­tion "Asyn­chron­ous Im­ple­ment­a­tion with MSMQ" in Chapter 9, "In­ter­lude: Com­posed Mes­saging").

                                                                                                                                                                                                    网关链提供不同的抽象级别

                                                                                                                                                                                                    A Chain of Gate­ways Provides Dif­fer­ent Levels of Ab­strac­tion

                                                                                                                                                                                                    图形/10inf05.gif

                                                                                                                                                                                                    处理消息异常

                                                                                                                                                                                                    Deal­ing with Mes­saging Ex­cep­tions

                                                                                                                                                                                                    除了简化应用程序编码之外,消息传递网关的目的还在于消除应用程序代码对特定消息传递技术的依赖。通过将任何特定于消息传递的方法调用包装在消息传递网关接口后面,可以轻松实现这一点。然而,大多数消息传递层都会抛出特定于消息传递的异常,例如JMS 引发的 InvalidDestinationException 。如果我们真的想让我们的应用程序代码独立于消息传递库,那么消息传递网关必须捕获任何特定于消息传递的异常并抛出特定于应用程序的(或通用)异常。这段代码可能会有点乏味,但如果我们必须切换底层实现(例如从 JMS 到 Web 服务),它会非常有帮助。

                                                                                                                                                                                                    Be­sides making coding the ap­plic­a­tion sim­pler, the intent of the Mes­saging Gate­way is also to elim­in­ate de­pend­en­cies of the ap­plic­a­tion code on spe­cific mes­saging tech­no­lo­gies. This is easy to do by wrap­ping any mes­saging-spe­cific method calls behind the Mes­saging Gate­way in­ter­face. How­ever, most mes­saging layers throw mes­saging-spe­cific ex­cep­tions, such as the In­val­id­Des­tin­a­tionEx­cep­tion raised by JMS. If we really want to make our ap­plic­a­tion code in­de­pend­ent from the mes­saging lib­rary, the Mes­saging Gate­way has to catch any mes­saging-spe­cific ex­cep­tion and throw an ap­plic­a­tion-spe­cific (or a gen­eric) ex­cep­tion in­stead. This code can get a little te­di­ous, but it is very help­ful if we ever have to switch the un­der­ly­ing im­ple­ment­a­tions, for in­stance, from JMS to Web ser­vices.

                                                                                                                                                                                                    生成网关

                                                                                                                                                                                                    Gen­er­at­ing Gate­ways

                                                                                                                                                                                                    在许多情况下,我们可以根据外部资源公开的元数据生成消息传递网关代码。这在 Web 服务领域很常见。几乎每个供应商或开源平台都提供了wsdl2java等工具连接到外部 Web 服务公开的 Web 服务描述语言 (WSDL)。该工具生成 Java(或 C#,或您需要的任何语言)类,封装所有讨厌的 SOAP 内容并公开简单的函数调用。我们创建了一个类似的工具,可以从 TIBCO 存储库读取消息模式定义,并为模拟模式定义的类创建 Java 源代码。这允许应用程序开发人员发送正确类型的 TIBCO ActiveEnterprise 消息,而无需学习 TIBCO API。

                                                                                                                                                                                                    In many situ­ations, we can gen­er­ate the Mes­saging Gate­way code from metadata ex­posed by the ex­ternal re­source. This is common in the world of Web ser­vices. Almost every vendor or open source plat­form provides a tool such as wsdl2­java that con­nects to the Web Ser­vice De­scrip­tion Lan­guage (WSDL) ex­posed by an ex­ternal Web ser­vice. The tool gen­er­ates Java (or C#, or whatever lan­guage you need) classes that en­cap­su­late all the nasty SOAP stuff and expose a simple func­tion call. We cre­ated a sim­ilar tool that can read mes­sage schema defin­i­tions off the TIBCO re­pos­it­ory and cre­ates Java source code for a class that mimics the schema defin­i­tion. This allows ap­plic­a­tion de­ve­lopers to send cor­rectly typed TIBCO Act­iveEn­ter­prise mes­sages without having to learn the TIBCO API.

                                                                                                                                                                                                    使用网关进行测试

                                                                                                                                                                                                    Using Gate­ways for Test­ing

                                                                                                                                                                                                    消息传递网关是出色的测试工具。因为我们将所有消息传递代码包装在一个狭窄的、特定于域的接口后面,所以我们可以轻松创建该接口的虚拟实现。我们只是将接口和实现分开,并提供两种实现:一种是访问消息传递基础设施的“真实”实现,另一种是用于测试目的的“假”实现(见图)。伪造的实现充当服务存根[ EAA ],并允许我们在不依赖消息传递的情况下测试应用程序。服务存根还可用于调试使用事件驱动消息传递网关的应用程序。例如,事件驱动消息传递网关的简单测试存根可以直接从请求方法调用回调(或委托),从而在一个线程中有效地执行请求和响应处理。这可以极大地简化逐步调试。

                                                                                                                                                                                                    Mes­saging Gate­ways make great test­ing vehicles. Be­cause we wrapped all the mes­saging code behind a narrow, domain-spe­cific in­ter­face, we can easily create a dummy im­ple­ment­a­tion of this in­ter­face. We simply sep­ar­ate in­ter­face and im­ple­ment­a­tion and provide two im­ple­ment­a­tions: one "real" im­ple­ment­a­tion that ac­cesses the mes­saging in­fra­struc­ture and a "fake" im­ple­ment­a­tion for test­ing pur­poses (see figure). The fake im­ple­ment­a­tion acts as a Ser­vice Stub [EAA] and allows us to test the ap­plic­a­tion without any de­pend­ency on mes­saging. A Ser­vice Stub can also be useful to debug an ap­plic­a­tion that uses an event-driven Mes­saging Gate­way. For ex­ample, a simple test stub for an event-driven Mes­saging Gate­way can simply invoke the call­back (or del­eg­ate) right from the re­quest method, ef­fect­ively ex­ecut­ing both the re­quest and the re­sponse pro­cess­ing in one thread. This can sim­plify step-by-step de­bug­ging enorm­ously.

                                                                                                                                                                                                    网关作为测试工具

                                                                                                                                                                                                    Gate­ways as a Test­ing Tool

                                                                                                                                                                                                    图形/10inf06.gif

                                                                                                                                                                                                    示例: MSMQ 中的异步贷款经纪人网关

                                                                                                                                                                                                    Ex­ample: Asyn­chron­ous Loan Broker Gate­way in MSMQ

                                                                                                                                                                                                    此示例显示了第 9 章“插曲:组合消息传递”中介绍的贷款经纪人示例的一部分(请参阅“使用 MSMQ 进行异步实现”)。

                                                                                                                                                                                                    This ex­ample shows a piece of the Loan Broker ex­ample in­tro­duced in Chapter 9, "In­ter­lude: Com­posed Mes­saging" (see "Asyn­chron­ous Im­ple­ment­a­tion with MSMQ").

                                                                                                                                                                                                    
                                                                                                                                                                                                    公共委托 void OnCreditReplyEvent(CreditBureauReply
                                                                                                                                                                                                    图形/ccc.gifCreditReply,对象 ACT);
                                                                                                                                                                                                    
                                                                                                                                                                                                    内部结构 CreditRequestProcess
                                                                                                                                                                                                    {
                                                                                                                                                                                                        公共 int CorrelationID;
                                                                                                                                                                                                        公共对象 ACT;
                                                                                                                                                                                                        公共 OnCreditReplyEvent 回调;
                                                                                                                                                                                                    }
                                                                                                                                                                                                    内部类 CreditBureauGateway
                                                                                                                                                                                                    {
                                                                                                                                                                                                        受保护的 IMessageSender 信用请求队列;
                                                                                                                                                                                                        受保护的 IMessageReceiver 信用回复队列;
                                                                                                                                                                                                    
                                                                                                                                                                                                        受保护的 IDictionary activeProcesses = (IDictionary)(new
                                                                                                                                                                                                    图形/ccc.gif哈希表());
                                                                                                                                                                                                    
                                                                                                                                                                                                        受保护的随机随机 = new Random();
                                                                                                                                                                                                    
                                                                                                                                                                                                        公共无效监听()
                                                                                                                                                                                                        {
                                                                                                                                                                                                            CreditReplyQueue.Begin();
                                                                                                                                                                                                        }
                                                                                                                                                                                                    
                                                                                                                                                                                                        公共无效GetCreditScore(CreditBureauRequest quoteRequest,
                                                                                                                                                                                                                                   OnCreditReplyEvent OnCreditResponse,
                                                                                                                                                                                                                                   对象行动)
                                                                                                                                                                                                        {
                                                                                                                                                                                                            消息 requestMessage = new Message(quoteRequest);
                                                                                                                                                                                                            requestMessage.ResponseQueue = CreditReplyQueue.GetQueue();
                                                                                                                                                                                                            requestMessage.AppSpecific = random.Next();
                                                                                                                                                                                                    
                                                                                                                                                                                                            CreditRequestProcess processInstance = 新
                                                                                                                                                                                                    图形/ccc.gifCreditRequestProcess();
                                                                                                                                                                                                            processInstance.ACT = ACT;
                                                                                                                                                                                                            processInstance.callback = OnCreditResponse;
                                                                                                                                                                                                            processInstance.CorrelationID = requestMessage.AppSpecific;
                                                                                                                                                                                                    
                                                                                                                                                                                                            CreditRequestQueue.Send(requestMessage);
                                                                                                                                                                                                    
                                                                                                                                                                                                            activeProcesses.Add(processInstance.CorrelationID,
                                                                                                                                                                                                    图形/ccc.gif进程实例);
                                                                                                                                                                                                        }
                                                                                                                                                                                                    
                                                                                                                                                                                                        私人无效OnCreditResponse(消息msg)
                                                                                                                                                                                                        {
                                                                                                                                                                                                            msg.Formatter = GetFormatter();
                                                                                                                                                                                                    
                                                                                                                                                                                                            CreditBureau回复replyStruct;
                                                                                                                                                                                                            尝试
                                                                                                                                                                                                            {
                                                                                                                                                                                                                if (msg.Body 是 CreditBureauReply)
                                                                                                                                                                                                                {
                                                                                                                                                                                                                    replyStruct = (CreditBureauReply)msg.Body;
                                                                                                                                                                                                                    int CorrelationID = msg.AppSpecific;
                                                                                                                                                                                                    
                                                                                                                                                                                                                    if (activeProcesses.Contains(CorrelationID))
                                                                                                                                                                                                                    {
                                                                                                                                                                                                                        CreditRequestProcess processInstance =
                                                                                                                                                                                                                            (信用请求流程)
                                                                                                                                                                                                    图形/ccc.gif(activeProcesses[CorrelationID]);
                                                                                                                                                                                                                        processInstance.callback(replyStruct,
                                                                                                                                                                                                    图形/ccc.gif进程实例.ACT);
                                                                                                                                                                                                                        activeProcesses.Remove(CorrelationID);
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                    别的
                                                                                                                                                                                                                    {
                                                                                                                                                                                                                        Console.WriteLine("传入信用响应
                                                                                                                                                                                                    图形/ccc.gif与任何请求都不匹配");
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                }
                                                                                                                                                                                                                别的
                                                                                                                                                                                                                { Console.WriteLine("非法回复。"); }
                                                                                                                                                                                                            }
                                                                                                                                                                                                            捕获(异常 e)
                                                                                                                                                                                                            {
                                                                                                                                                                                                                Console.WriteLine("异常:{0}", e.ToString());
                                                                                                                                                                                                            }
                                                                                                                                                                                                        }
                                                                                                                                                                                                    }
                                                                                                                                                                                                    
                                                                                                                                                                                                    
                                                                                                                                                                                                    public del­eg­ate void On­CreditReplyEvent(Cred­it­Bur­eauReply
                                                                                                                                                                                                     creditReply, Object ACT);
                                                                                                                                                                                                    
                                                                                                                                                                                                    in­ternal struct CreditRe­quest­Pro­cess
                                                                                                                                                                                                    {
                                                                                                                                                                                                        public int Cor­rel­a­tionID;
                                                                                                                                                                                                        public Object ACT;
                                                                                                                                                                                                        public On­CreditReplyEvent call­back;
                                                                                                                                                                                                    }
                                                                                                                                                                                                    in­ternal class Cred­it­Bur­eau­G­ate­way
                                                                                                                                                                                                    {
                                                                                                                                                                                                        pro­tec­ted IMes­sageSender creditRe­questQueue;
                                                                                                                                                                                                        pro­tec­ted IMes­sageRe­ceiver creditReplyQueue;
                                                                                                                                                                                                    
                                                                                                                                                                                                        pro­tec­ted IDic­tion­ary act­ive­Pro­cesses = (IDic­tion­ary)(new
                                                                                                                                                                                                     Hasht­able());
                                                                                                                                                                                                    
                                                                                                                                                                                                        pro­tec­ted Random random = new Random();
                                                                                                                                                                                                    
                                                                                                                                                                                                        public void Listen()
                                                                                                                                                                                                        {
                                                                                                                                                                                                            creditReplyQueue.Begin();
                                                                                                                                                                                                        }
                                                                                                                                                                                                    
                                                                                                                                                                                                        public void GetCred­itScore(Cred­it­Bur­eauRe­quest quote­Re­quest,
                                                                                                                                                                                                                                   On­CreditReplyEvent On­CreditRe­sponse,
                                                                                                                                                                                                                                   Object ACT)
                                                                                                                                                                                                        {
                                                                                                                                                                                                            Mes­sage re­quest­Mes­sage = new Mes­sage(quote­Re­quest);
                                                                                                                                                                                                            re­quest­Mes­sage.Re­spon­se­Queue = creditReplyQueue.GetQueue();
                                                                                                                                                                                                            re­quest­Mes­sage.AppSpe­cific = random.Next();
                                                                                                                                                                                                    
                                                                                                                                                                                                            CreditRe­quest­Pro­cess pro­cessIn­stance = new
                                                                                                                                                                                                     CreditRe­quest­Pro­cess();
                                                                                                                                                                                                            pro­cessIn­stance.ACT = ACT;
                                                                                                                                                                                                            pro­cessIn­stance.call­back = On­CreditRe­sponse;
                                                                                                                                                                                                            pro­cessIn­stance.Cor­rel­a­tionID = re­quest­Mes­sage.AppSpe­cific;
                                                                                                                                                                                                    
                                                                                                                                                                                                            creditRe­questQueue.Send(re­quest­Mes­sage);
                                                                                                                                                                                                    
                                                                                                                                                                                                            act­ive­Pro­cesses.Add(pro­cessIn­stance.Cor­rel­a­tionID,
                                                                                                                                                                                                     pro­cessIn­stance);
                                                                                                                                                                                                        }
                                                                                                                                                                                                    
                                                                                                                                                                                                        private void On­CreditRe­sponse(Mes­sage msg)
                                                                                                                                                                                                        {
                                                                                                                                                                                                            msg.Format­ter =  Get­Format­ter();
                                                                                                                                                                                                    
                                                                                                                                                                                                            Cred­it­Bur­eauReply reply­S­truct;
                                                                                                                                                                                                            try
                                                                                                                                                                                                            {
                                                                                                                                                                                                                if (msg.Body is Cred­it­Bur­eauReply)
                                                                                                                                                                                                                {
                                                                                                                                                                                                                    reply­S­truct = (Cred­it­Bur­eauReply)msg.Body;
                                                                                                                                                                                                                    int Cor­rel­a­tionID = msg.AppSpe­cific;
                                                                                                                                                                                                    
                                                                                                                                                                                                                    if (act­ive­Pro­cesses.Con­tains(Cor­rel­a­tionID))
                                                                                                                                                                                                                    {
                                                                                                                                                                                                                        CreditRe­quest­Pro­cess pro­cessIn­stance =
                                                                                                                                                                                                                            (CreditRe­quest­Pro­cess)
                                                                                                                                                                                                    (act­ive­Pro­cesses[Cor­rel­a­tionID]);
                                                                                                                                                                                                                        pro­cessIn­stance.call­back(reply­S­truct,
                                                                                                                                                                                                     pro­cessIn­stance.ACT);
                                                                                                                                                                                                                        act­ive­Pro­cesses.Remove(Cor­rel­a­tionID);
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                    else
                                                                                                                                                                                                                    {
                                                                                                                                                                                                                        Con­sole.WriteLine("In­com­ing credit re­sponse
                                                                                                                                                                                                     does not match any re­quest");
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                }
                                                                                                                                                                                                                else
                                                                                                                                                                                                                { Con­sole.WriteLine("Il­legal reply."); }
                                                                                                                                                                                                            }
                                                                                                                                                                                                            catch (Ex­cep­tion e)
                                                                                                                                                                                                            {
                                                                                                                                                                                                                Con­sole.WriteLine("Ex­cep­tion: {0}", e.To­String());
                                                                                                                                                                                                            }
                                                                                                                                                                                                        }
                                                                                                                                                                                                    }
                                                                                                                                                                                                    

                                                                                                                                                                                                    您会注意到公共方法GetCreditScore和公共委托OnCreditReplyEvent根本不引用消息传递。此实现允许调用应用程序将任意对象引用作为 ACT 传递。CreditBureauGateway将此对象引用存储在由请求消息相关标识符索引的字典中。当回复消息到达时,CreditBureauGateway 可以检索与出站请求消息关联的数据。调用应用程序不必担心消息如何关联。

                                                                                                                                                                                                    You will notice that the public method GetCred­itScore and the public del­eg­ate On­CreditReplyEvent make no ref­er­ences to mes­saging at all. This im­ple­ment­a­tion allows the call­ing ap­plic­a­tion to pass an ar­bit­rary object ref­er­ence as an ACT. The Cred­it­Bur­eau­G­ate­way stores this object ref­er­ence in a dic­tion­ary in­dexed by the Cor­rel­a­tion Iden­ti­fier of the re­quest mes­sage. When the reply mes­sage ar­rives, the Cred­it­Bur­eau­G­ate­way can re­trieve the data that was as­so­ci­ated with the out­bound re­quest mes­sage. The call­ing ap­plic­a­tion does not have to worry about how the mes­sages are cor­rel­ated.



                                                                                                                                                                                                      消息传递映射器

                                                                                                                                                                                                      Messaging Mapper

                                                                                                                                                                                                      当应用程序使用消息传递时,消息的数据通常源自应用程序的域对象。如果我们使用文档消息,消息本身可能直接代表一个或多个域对象。如果我们使用命令消息,与命令关联的一些数据字段也可能从域对象中提取。消息和对象之间存在一些明显的差异。例如,大多数对象依赖于对象引用和继承关系形式的关联。许多消息传递基础设施不支持这些概念,因为它们必须能够与一系列应用程序通信,其中一些应用程序可能根本不是面向对象的。

                                                                                                                                                                                                      When ap­plic­a­tions use Mes­saging, the Mes­sages' data is often de­rived from the ap­plic­a­tions' domain ob­jects. If we use a Doc­u­ment Mes­sage, the mes­sage itself may dir­ectly rep­res­ent one or more domain ob­jects. If we use a Com­mand Mes­sage, some of the data fields as­so­ci­ated with the com­mand are likely to be ex­trac­ted from domain ob­jects as well. There are some dis­tinct dif­fer­ences between mes­sages and ob­jects. For ex­ample, most ob­jects rely on as­so­ci­ations in the form of object ref­er­ences and in­her­it­ance re­la­tion­ships. Many mes­saging in­fra­struc­tures do not sup­port these con­cepts be­cause they have to be able to com­mu­nic­ate with a range of ap­plic­a­tions, some of which may not be object-ori­ented at all.

                                                                                                                                                                                                      如何在域对象和消息传递基础设施之间移动数据,同时保持两者相互独立?

                                                                                                                                                                                                      How do you move data between domain ob­jects and the mes­saging in­fra­struc­ture while keep­ing the two in­de­pend­ent of each other?



                                                                                                                                                                                                      为什么我们不能让我们的消息看起来与域对象完全相同并让问题消失?在许多情况下,我们无法控制消息格式,因为它是由规范数据模型或通用消息传递标准(例如 ebXML)定义的。我们仍然可以以与域对象相对应的格式发布消息,并使用消息传递层内的消息转换器对通用消息格式进行必要的转换。这种方法通常由不允许在应用程序内部进行转换的第三方系统适配器(例如数据库适配器)使用。

                                                                                                                                                                                                      Why can't we make our mes­sages look ex­actly like the domain ob­jects and make the prob­lem go away? In many cases, we are not in con­trol of the mes­sage format be­cause it is defined by a Ca­non­ical Data Model or a common mes­saging stand­ard (e.g., ebXML). We could still pub­lish the mes­sage in a format that cor­res­ponds to the domain object and use a Mes­sage Trans­lator inside the mes­saging layer to make the ne­ces­sary trans­form­a­tion to the common mes­sage format. This ap­proach is com­monly used by ad­apters to third-party sys­tems that do not allow trans­form­a­tion inside the ap­plic­a­tion (e.g., a data­base ad­apter).

                                                                                                                                                                                                      或者,域层可以以所需的格式创建和发布消息,而不需要单独的消息转换器。此选项很可能会带来更好的性能,因为我们不发布中间消息。此外,如果我们的领域模型包含许多小对象,那么首先将它们组合成单个消息可能会有益于简化路由并提高消息传递层内的效率。即使我们能够承担额外的转换步骤,如果我们想要创建模仿域对象的消息,我们也会遇到限制。这种方法的缺点是域变得依赖于消息格式,如果消息格式发生变化,这使得域维护变得困难。

                                                                                                                                                                                                      Al­tern­at­ively, the domain layer can create and pub­lish a mes­sage in the re­quired format without the need for a sep­ar­ate Mes­sage Trans­lator. This option most likely res­ults in better per­form­ance be­cause we do not pub­lish an in­ter­me­di­ate mes­sage. Also, if our domain model con­tains many small ob­jects, it may be be­ne­fi­cial to com­bine them into a single mes­sage first to sim­plify rout­ing and im­prove ef­fi­ciency inside the mes­saging layer. Even if we can afford the ad­di­tional trans­form­a­tion step, we will run into lim­it­a­tions if we want to create mes­sages that mimic domain ob­jects. The short­com­ing of this ap­proach is that the domain be­comes de­pend­ent on the mes­sage format, which makes domain main­ten­ance dif­fi­cult if the mes­sage format changes.

                                                                                                                                                                                                      大多数消息传递基础设施都支持“消息”对象的概念作为 API 的一部分。该消息对象封装了要通过通道发送的数据。在大多数情况下,此消息对象只能包含标量数据类型,例如字符串、数字或日期,但不支持继承或对象引用。这是 RPC 式通信(即 RMI)和异步消息传递系统之间的主要区别之一。假设我们发送一条异步消息,其中包含对组件的对象引用。为了处理消息,组件必须解析对象引用。它将通过从消息源请求对象来完成此操作。然而,请求-回复交互首先会破坏使用异步消息传递的一些动机(即组件之间的松散耦合)。更糟糕的是,当订阅者收到异步消息时,所引用的对象可能不再存在于源系统中。

                                                                                                                                                                                                      Most mes­saging in­fra­struc­tures sup­port the notion of a "Mes­sage" object as part of the API. This mes­sage object en­cap­su­lates the data to be sent over a chan­nel. In most cases, this mes­sage object can con­tain only scalar data­types such as strings, num­bers, or dates, but does not sup­port in­her­it­ance or object ref­er­ences. This is one of the key dif­fer­ences between RPC-style com­mu­nic­a­tions (i.e., RMI) and asyn­chron­ous mes­saging sys­tems. Let's assume we send an asyn­chron­ous mes­sage con­tain­ing an object ref­er­ence to a com­pon­ent. In order to pro­cess the mes­sage, the com­pon­ent would have to re­solve the object ref­er­ence. It would do this by re­quest­ing the object from the mes­sage source. How­ever, re­quest-reply in­ter­ac­tion would defeat some of the mo­tiv­a­tions of using asyn­chron­ous mes­saging in the first place (i.e., loose coup­ling between com­pon­ents). Worse yet, by the time the asyn­chron­ous mes­sage is re­ceived by the sub­scriber, the ref­er­enced object may no longer exist in the source system.

                                                                                                                                                                                                      解决对象引用问题的一种尝试是遍历对象的依赖关系树并将所有依赖对象包含在消息中。例如,如果一个Order对象引用五个OrderItem对象,我们将在消息中包含这五个对象。这确保接收者可以访问“根”对象的所有数据引用。但是,如果我们使用具有许多相互关联的对象的细粒度域对象模型,消息的大小可能会迅速爆炸。人们希望对消息中包含的内容和不包含的内容有更多的控制。

                                                                                                                                                                                                      One at­tempt to re­solve the issue of object ref­er­ences is to tra­verse the de­pend­ency tree of an object and in­clude all de­pend­ent ob­jects in the mes­sage. For ex­ample, if an Order object ref­er­ences five Or­der­Item ob­jects, we would in­clude the five ob­jects in the mes­sage. This en­sures that the re­ceiver has access to all data ref­er­ences by the "root" object. How­ever, if we use a fine-grained domain object model with many in­ter­re­lated ob­jects, mes­sages can quickly ex­plode in size. It would be de­sir­able to have more con­trol over what is in­cluded in a mes­sage and what is not.

                                                                                                                                                                                                      让我们假设我们的域对象是独立的,并且没有任何对其他对象的引用。我们仍然不能简单地将整个域对象放入消息中,因为大多数消息传递基础结构不支持对象,因为它们必须与语言无关(JMS 接口 ObjectMessage 和 .NET 的 System.Messaging 命名空间中的Message例外,因为这些消息传递系统是特定于语言的 [Java] 或特定于平台的 [.NET CLR])。我们可以考虑将对象序列化为字符串并将其存储在称为“数据”的字符串字段中,几乎每个消息系统都支持该字段。然而,这种方法也有缺点。首先,一个消息路由器将无法将对象属性用于路由目的,因为该字符串字段对于消息传递层来说是“不透明的”。它还会使测试和调试变得困难,因为我们必须破译数据字段的内容。此外,构建所有消息以使其仅包含单个字符串字段将不允许我们按消息类型路由消息,因为所有消息对于基础设施来说看起来都是相同的。验证消息的正确格式也很困难,因为消息传递基础设施不会验证数据字段内的任何内容。最后,我们将无法使用语言运行时库提供的序列化工具,因为这些表示通常不跨语言兼容。所以,

                                                                                                                                                                                                      Let's assume for a moment that our domain object is self-con­tained and does not have any ref­er­ences to other ob­jects. We still cannot simply stick the whole domain object into a mes­sage, as most mes­saging in­fra­struc­tures do not sup­port ob­jects be­cause they have to be lan­guage-in­de­pend­ent (the JMS in­ter­face Ob­ject­Mes­sage and the Mes­sage class in .NET's System.Mes­saging namespace are ex­cep­tions, since these mes­saging sys­tems are either lan­guage-spe­cific [Java] or plat­form-spe­cific [.NET CLR]). We could think of seri­al­iz­ing the object into a string and stor­ing it in a string field called "data," which is sup­por­ted by pretty much every mes­saging system. How­ever, this ap­proach has dis­ad­vant­ages as well. First, a Mes­sage Router would not be able to use object prop­er­ties for rout­ing pur­poses be­cause this string field would be "opaque" to the mes­saging layer. It would also make test­ing and de­bug­ging dif­fi­cult, be­cause we would have to de­cipher the con­tents of the data field. Also, con­struct­ing all mes­sages so that they con­tain just a single string field would not allow us to route mes­sages by mes­sage type be­cause all mes­sages look the same to the in­fra­struc­ture. It would also be dif­fi­cult to verify the cor­rect format of the mes­sage be­cause the mes­saging in­fra­struc­ture would not verify any­thing inside the data field. Fi­nally, we would not be able to use the seri­al­iz­a­tion fa­cil­it­ies provided by the lan­guage runtime lib­rar­ies be­cause these present­a­tions are usu­ally not com­pat­ible across lan­guages. So, we would have to write our own seri­al­iz­a­tion code.

                                                                                                                                                                                                      一些消息传递基础设施现在支持消息内的 XML 字段,以便我们可以将对象序列化为 XML。这可以减轻一些缺点,因为现在消息更容易破译,并且某些消息传递层可以直接访问 XML 字符串内的元素。然而,我们现在必须处理相当详细的消息和有限的数据类型验证。另外,我们仍然需要创建将对象转换为 XML 并返回的代码。根据我们使用的编程语言,这可能非常复杂,特别是如果我们使用不支持反射的旧语言。

                                                                                                                                                                                                      Some mes­saging in­fra­struc­tures now sup­port XML fields inside mes­sages so that we could seri­al­ize ob­jects into XML. This can al­le­vi­ate some of the dis­ad­vant­ages be­cause the mes­sages are easier to de­cipher now and some mes­saging layers can access ele­ments inside an XML string dir­ectly. How­ever, we now have to deal with quite verb­ose mes­sages and lim­ited data­type val­id­a­tion. Plus, we still have to create code that trans­lates an object into XML and back. De­pend­ing on the pro­gram­ming lan­guage we use, this could be quite com­plex, es­pe­cially if we use an older lan­guage that does not sup­port re­flec­tion.

                                                                                                                                                                                                      出于多种原因,我们强烈建议将此映射代码与域对象分开。首先,我们可能不想将涉及低级语言功能的代码与应用程序逻辑混合在一起。在许多情况下,我们会有一组程序员专门负责消息传递层,而另一组则专注于领域逻辑。将两段代码粘贴到一个对象中将使团队难以并行工作。

                                                                                                                                                                                                      We are well ad­vised to sep­ar­ate this map­ping code from the domain object for a number of reas­ons. First of all, we may not want to blend code that con­cerns itself with low-level lan­guage fea­tures with ap­plic­a­tion logic. In many cases, we will have a group of pro­gram­mers ded­ic­ated to work­ing with the mes­saging layer, while an­other group fo­cuses on the domain logic. Stick­ing both pieces of code into one object will make it dif­fi­cult for the teams to work in par­al­lel.

                                                                                                                                                                                                      其次,将映射代码合并到域对象内使得域对象依赖于消息传送基础设施,因为映射代码需要调用消息传送API(例如,实例化消息对象)在大多数情况下,这种依赖性是不可取的,因为它会阻止在不使用消息传递或使用其他供应商的消息传递基础结构的另一个上下文中重用域对象。结果,我们将严重阻碍领域对象的可重用性。

                                                                                                                                                                                                      Second, in­cor­por­at­ing map­ping code inside the domain object makes the domain object de­pend­ent on the mes­saging in­fra­struc­ture be­cause the map­ping code will need to make calls into the mes­saging API (e.g., to in­stan­ti­ate the Mes­sage object). In most cases, this de­pend­ency is not de­sir­able be­cause it pre­vents the reuse of the domain ob­jects in an­other con­text that does not use mes­saging or that uses an­other vendor's mes­saging in­fra­struc­ture. As a result, we would ser­i­ously impede the re­usab­il­ity of the domain ob­jects.

                                                                                                                                                                                                      我们经常看到人们编写包装消息传递基础设施 API 的“抽象层”,从而有效地使处理消息传递的代码独立于消息传递 API。这样的层提供了一定程度的间接性,因为它将消息传递接口与消息传递实现分开。因此,即使我们必须切换到另一个供应商的消息传递层,我们也可以重用消息传递相关的代码。我们需要做的就是实现一个新的抽象层,将消息传递接口转换为新的 API。然而,这种方法并没有解决域对象对消息传递层的依赖性。域对象现在将包含对抽象消息传递接口的引用,而不是对特定于供应商的消息传递 API 的引用。

                                                                                                                                                                                                      We often see people write "ab­strac­tion layers" that wrap the mes­saging in­fra­struc­ture API, ef­fect­ively making the code that deals with mes­saging in­de­pend­ent from the mes­saging API. Such a layer provides a level of in­dir­ec­tion be­cause it sep­ar­ates the mes­saging in­ter­face from the mes­saging im­ple­ment­a­tion. There­fore, we can reuse the mes­saging-re­lated code even if we have to switch to an­other vendor's mes­saging layer. All we need to do is im­ple­ment a new ab­strac­tion layer that trans­lates the mes­saging in­ter­face to the new API. How­ever, this ap­proach does not re­solve the de­pend­ency of the domain ob­jects on the mes­saging layer. The domain ob­jects would now con­tain ref­er­ences to the ab­strac­ted mes­saging in­ter­face as op­posed to the vendor-spe­cific mes­saging API. But we still cannot use the domain ob­jects in a con­text that does not use mes­saging.

                                                                                                                                                                                                      许多消息由多个域对象组成。由于我们无法通过消息传递基础结构传递对象引用,因此我们可能需要包含来自其他对象的字段。在某些情况下,我们可能会在一条消息中包含所有依赖对象的整个“依赖树”。哪个类应该保存映射代码?同一对象可能是与不同​​对象组合的多种消息类型的一部分,因此这个问题没有简单的答案。

                                                                                                                                                                                                      Many mes­sages are com­posed of more than one domain object. Since we cannot pass object ref­er­ences through the mes­saging in­fra­struc­ture, it is likely that we need to in­clude fields from other ob­jects. In some cases, we may in­clude the whole "de­pend­ency tree" of all de­pend­ent ob­jects inside one mes­sage. Which class should hold the map­ping code? The same object may be part of mul­tiple mes­sage types com­bined with dif­fer­ent ob­jects, so there is no easy answer to this ques­tion.

                                                                                                                                                                                                      创建一个单独的消息传递映射器,其中包含消息传递基础结构和域对象之间的映射逻辑。对象和基础设施都不知道消息传递映射器的存在。

                                                                                                                                                                                                      Create a sep­ar­ate Mes­saging Mapper that con­tains the map­ping logic between the mes­saging in­fra­struc­ture and the domain ob­jects. Neither the ob­jects nor the in­fra­struc­ture have know­ledge of the Mes­saging Mapper's ex­ist­ence.

                                                                                                                                                                                                      图形/10inf07.gif



                                                                                                                                                                                                      消息传递映射器访问一个或多个域对象,并将它们转换为消息传递通道所需的消息。它还执行相反的功能,根据传入消息创建或更新域对象。由于消息传递映射器是作为引用域对象和消息传递层的单独类实现的,因此两层都不知道另一层。这些层甚至不知道消息映射器

                                                                                                                                                                                                      The Mes­saging Mapper ac­cesses one or more domain ob­jects and con­verts them into a mes­sage as re­quired by the mes­saging chan­nel. It also per­forms the op­pos­ite func­tion, cre­at­ing or up­dat­ing domain ob­jects based on in­com­ing mes­sages. Since the Mes­saging Mapper is im­ple­men­ted as a sep­ar­ate class that ref­er­ences both the domain object(s) and the mes­saging layer, neither layer is aware of the other. The layers don't even know about the Mes­saging Mapper.

                                                                                                                                                                                                      消息传递映射器映射器模式 [ EAA ]的特化。它与Data Mapper [ EAA ]有一些相似之处。任何研究过 OR(对象关系)映射策略的人都会理解在使用不同范例的层之间映射数据的复杂性。消息映射器中固有的问题同样复杂,对所有可能方面的详细讨论超出了本书的范围。[ EAA ]中的许多数据源架构模式对于任何关心创建消息映射器层的人来说都值得一读。

                                                                                                                                                                                                      The Mes­saging Mapper is a spe­cial­iz­a­tion of the Mapper pat­tern [EAA]. It shares some ana­lo­gies with Data Mapper [EAA]. Anyone who has worked on O-R (Object-Re­la­tional) map­ping strategies will un­der­stand the com­plex­it­ies of map­ping data between layers that use dif­fer­ent paradigms. The issues in­her­ent in the Mes­saging Mapper are sim­il­arly com­plex, and a de­tailed dis­cus­sion of all pos­sible as­pects is beyond the scope of this book. Many of the Data Source Ar­chi­tec­tural Pat­terns in [EAA] make a good read for anyone con­cerned with cre­at­ing a Mes­saging Mapper layer.

                                                                                                                                                                                                      消息传递映射器与围绕消息传递 API 的抽象层的常用概念不同。在抽象层的情况下,域对象不知道消息传递 API,但它们确实知道抽象层(抽象层本质上执行消息传递网关的功能。对于消息传递映射器来说,对象根本不知道我们正在处理消息传递。

                                                                                                                                                                                                      A Mes­saging Mapper is dif­fer­ent from the fre­quently used concept of an ab­strac­tion layer wrapped around the mes­saging API. In the case of an ab­strac­tion layer, the domain ob­jects do not know about the mes­saging API, but they do know about the ab­strac­tion layer (the ab­strac­tion layer es­sen­tially per­forms the func­tion of a Mes­saging Gate­way ). In the case of a Mes­saging Mapper, the ob­jects have no idea what­so­ever that we are deal­ing with mes­saging.

                                                                                                                                                                                                      消息映射器的意图与中介器 [ GoF ]类似,后者也用于分隔元素。但是在使用 Mediator 的情况下,元素都知道Mediator,而两个元素都不知道Messaging Mapper

                                                                                                                                                                                                      The intent of a Mes­saging Mapper is sim­ilar to that of a Me­di­ator [GoF], which is also used to sep­ar­ate ele­ments. In the case of a Me­di­ator, though, the ele­ments are aware of the Me­di­ator, whereas neither ele­ment is aware of the Mes­saging Mapper.

                                                                                                                                                                                                      如果域对象和消息传递基础设施都不知道消息传递映射器,那么如何调用它?在大多数情况下,消息传递映射器是通过消息传递基础结构或应用程序触发的事件来调用的。由于两者都不依赖于消息传递映射器,因此事件通知可以通过单独的代码片段或通过使消息传递映射器成为观察者模式[ GoF]来发生。例如,如果我们使用 JMS API 与消息传递基础设施交互,我们可以实现MessageListener接口来接收任何传入消息的通知。同样,我们可以使用观察者接收域对象内任何相关事件的通知并调用消息传递映射器。如果我们必须直接从应用程序调用消息传递映射器,我们应该定义一个消息传递映射器接口,以便应用程序至少不依赖于消息传递映射器实现。

                                                                                                                                                                                                      If neither the domain ob­jects nor the mes­saging in­fra­struc­ture know about the Mes­saging Mapper, how does it get in­voked? In most cases, the Mes­saging Mapper is in­voked through events triggered by either the mes­saging in­fra­struc­ture or the ap­plic­a­tion. Since neither one is de­pend­ent on the Mes­saging Mapper, the event no­ti­fic­a­tion can happen either through a sep­ar­ate piece of code or by making the Mes­saging Mapper an Ob­server pat­tern [GoF]. For ex­ample, if we use the JMS API to in­ter­face with the mes­saging in­fra­struc­ture, we can im­ple­ment the Mes­sageL­istener in­ter­face to be no­ti­fied of any in­com­ing mes­sages. Like­wise, we can use an Ob­server to be no­ti­fied of any rel­ev­ant events inside the domain ob­jects and to invoke the Mes­saging Mapper. If we have to invoke the Mes­saging Mapper dir­ectly from the ap­plic­a­tion, we should define a Mes­saging Mapper in­ter­face so that the ap­plic­a­tion does at least not depend on the Mes­saging Mapper im­ple­ment­a­tion.

                                                                                                                                                                                                      减少编码负担

                                                                                                                                                                                                      Re­du­cing the Coding Burden

                                                                                                                                                                                                      一些消息映射器实现可能包含大量重复代码:从域对象获取字段并将其存储在消息对象中。继续到下一个字段并重复,直到完成所有字段。这可能非常乏味,而且还让人怀疑代码重复。我们有许多工具可以帮助我们避免这种单调乏味的情况。首先,我们可以编写一个通用的消息传递映射器它使用反射以通用方式从域对象中提取字段。例如,它可以遍历域对象内所有字段的列表,并将其存储在消息对象中的同名字段中。显然,只有当字段名称匹配时这才有效。根据我们之前的讨论,我们需要想出一些方法来解析对象引用,因为我们无法将它们存储在消息对象中。另一种方法是使用可配置的代码生成器来生成消息传递映射器代码。这使我们在字段命名方面具有更大的灵活性(消息字段名称和域对象字段名称不必匹配),并且我们可以设计巧妙的方法来处理对象引用。代码生成器的缺点是它们可能难以测试和调试,但如果我们使其足够通用,我们只需编写一次。

                                                                                                                                                                                                      Some Mes­saging Mapper im­ple­ment­a­tions may con­tain a lot of re­pet­it­ive code: Get a field from the domain object and store it in a mes­sage object. Go on to the next field and repeat until all fields are done. This can be pretty te­di­ous and also smells sus­pi­ciously like code du­plic­a­tion. We have a number of tools to help us avoid this tedium. First, we can write a gen­eric Mes­saging Mapper that uses re­flec­tion to ex­tract fields from a domain object in a gen­eric way. For ex­ample, it could tra­verse the list of all fields inside the domain object and store it in a field of the same name in the mes­sage object. Ob­vi­ously, this works only if the field names match. Ac­cord­ing to our pre­vi­ous dis­cus­sions, we need to come up with some way to re­solve object ref­er­ences, since we cannot store those in the mes­sage object. The al­tern­at­ive is to use a con­fig­ur­able code gen­er­ator to gen­er­ate the Mes­saging Mapper code. This allows us some more flex­ib­il­ity in the field naming (the mes­sage field name and the domain object field name do not have to match), and we can devise clever ways to deal with object ref­er­ences. The down­side of code gen­er­at­ors is that they can be dif­fi­cult to test and debug, but if we make it gen­eric enough, we have to write it only once.

                                                                                                                                                                                                      某些框架(例如 Microsoft .NET)具有将对象内置到 XML 中的对象序列化功能,反之亦然,从而消除了对象序列化中涉及的大量繁重工作。即使框架完成了将对象转换为消息的一些工作,这种转换也仅限于翻译的语法级别。让框架完成所有工作可能很诱人,但它只会创建与域对象一一对应的消息。正如我们之前所解释的,这可能并不理想,因为消息的约束和设计标准与域对象的约束和设计标准有很大不同。定义一组与所需消息结构相对应的“接口对象”并让框架在消息和这些对象之间进行转换可能是有意义的。这然后,消息传递映射器层将管理真实域对象和接口对象之间的转换。这些接口对象与数据传输对象 [ EAA ]有一些相似之处,尽管动机略有不同。

                                                                                                                                                                                                      Some frame­works, such as Mi­crosoft .NET, fea­ture built-in object seri­al­iz­a­tion of ob­jects into XML, and vice versa, and take away a lot of the grunt work in­volved in object seri­al­iz­a­tion. Even if the frame­work does some of the leg­work of con­vert­ing an object into a mes­sage, this con­ver­sion is lim­ited to the syn­tactic level of trans­la­tion. It might be tempt­ing to just let the frame­work do all the work, but it will just create mes­sages that cor­res­pond to the domain ob­jects one-to-one. As we ex­plained earlier, this may not be de­sir­able be­cause the con­straints and design cri­teria for mes­sages are quite dif­fer­ent from those for domain ob­jects. It may make sense to define a set of "in­ter­face ob­jects" that cor­res­pond to the de­sired mes­sages struc­ture and let the frame­work do the con­ver­sion between the mes­sages and these ob­jects. The Mes­saging Mapper layer will then manage the trans­la­tion between the true domain ob­jects and the in­ter­face ob­jects. These in­ter­face ob­jects bear some re­semb­lance to Data Trans­fer Ob­jects [EAA] even though the mo­tiv­a­tions are slightly dif­fer­ent.

                                                                                                                                                                                                      映射器与翻译器

                                                                                                                                                                                                      Mapper versus Trans­lator

                                                                                                                                                                                                      即使我们使用消息传递映射器,使用消息转换器将消息传递为符合规范仍然有意义。这为我们提供了额外的间接级别。我们可以使用消息映射器来解决诸如对象引用和数据类型转换之类的问题,并将结构映射留给消息转换器消息层内部。我们为这种额外的解耦付出的代价是创建一个额外的组件和一个小的性能损失。此外,有时在应用程序的编程语言内执行复杂的转换比使用集成供应商提供的拖放“涂鸦软件”更容易。

                                                                                                                                                                                                      Even if we use a Mes­saging Mapper, it still makes sense to use a Mes­sage Trans­lator to trans­late the mes­sages gen­er­ated by the Mes­saging Mapper into mes­sages com­pli­ant with the Ca­non­ical Data Model. This gives us an ad­di­tional level of in­dir­ec­tion. We can use the Mes­saging Mapper to re­solve issues such as object ref­er­ences and data­type con­ver­sions, and leave struc­tural map­pings to a Mes­sage Trans­lator inside the mes­saging layer. The price we pay for this ad­di­tional de­coup­ling is the cre­ation of an ad­di­tional com­pon­ent and a small per­form­ance pen­alty. Also, some­times it is easier to per­form com­plex trans­form­a­tions inside the ap­plic­a­tion's pro­gram­ming lan­guage than to use the drag-and-drop "doo­dle­ware" sup­plied by the in­teg­ra­tion vendor.

                                                                                                                                                                                                      如果我们同时使用消息映射器消息转换器,我们将在规范数据格式和域对象之间获得额外的间接级别。有人说,在计算机科学领域,每个问题都可以通过增加一层间接来解决,那么这个规则在这里是否成立呢?额外的间接使我们能够补偿消息传递层内规范模型的变化,而无需触及应用程序代码。它还允许我们通过保留繁琐的字段映射和数据类型更改(例如,将数字ZIP_Code字段转换为字母数字Postal_Code)来简化应用程序内部的映射逻辑字段)到针对此类工作优化的消息传递层的映射工具。然后,消息传递映射器将主要处理对象引用的解析和消除不必要的域对象细节。额外间接级别的明显缺点是域对象的更改现在可能需要更改消息映射器消息转换器。如果我们确实能够生成消息传递映射器代码,那么这个问题就基本上消失了。

                                                                                                                                                                                                      If we use both a Mes­saging Mapper and a Mes­sage Trans­lator, we gain an ad­di­tional level of in­dir­ec­tion between the ca­non­ical data format and the domain ob­jects. It has been said that com­puter sci­ence is the area where every prob­lem can be solved by adding just one more level of in­dir­ec­tion, so does this rule hold true here? The ad­di­tional in­dir­ec­tion gives us the abil­ity to com­pensate for changes in the ca­non­ical model inside the mes­saging layer without having to touch ap­plic­a­tion code. It also allows us to sim­plify the map­ping logic inside the ap­plic­a­tion by leav­ing te­di­ous field map­pings and data­type changes (e.g., a nu­meric ZIP_­Code field to an al­pha­nu­meric Postal_­Code field) to the map­ping tools of mes­saging layer that are op­tim­ized for this kind of work. The Mes­saging Mapper would then primar­ily deal with the res­ol­u­tion of object ref­er­ences and the elim­in­a­tion of un­ne­ces­sary domain object detail. The ap­par­ent down­side of the extra level of in­dir­ec­tion is that a change in the domain object may now re­quire changes to both the Mes­saging Mapper and the Mes­sage Trans­lator. If we did manage to gen­er­ate the Mes­saging Mapper code, this issue largely goes away.

                                                                                                                                                                                                      结合映射器和消息转换器

                                                                                                                                                                                                      Com­bin­ing Mapper and Mes­sage Trans­lator

                                                                                                                                                                                                      图形/10inf08.gif

                                                                                                                                                                                                      示例: JMS 中的消息传递映射器

                                                                                                                                                                                                      Ex­ample: Mes­saging Mapper in JMS

                                                                                                                                                                                                      Aggregator JMS 示例中的AuctionAggregate充当JMS 消息传递系统和 Bid 类之间的消息传递映射器。 addMessagegetResultMessage方法在 JMS 消息和 Bid对象进行转换。消息传递系统和Bid类都不知道这种交互。

                                                                                                                                                                                                      The Auc­tion­Ag­greg­ate class in the Ag­greg­ator JMS ex­ample acts as a Mes­saging Mapper between the JMS mes­saging system and the Bid class. The meth­ods addMes­sage and getRes­ult­Mes­sage con­vert between JMS mes­sages and Bid ob­jects. Neither the mes­saging system nor the Bid class has any know­ledge of this in­ter­ac­tion.



                                                                                                                                                                                                        交易客户

                                                                                                                                                                                                        Transactional Client

                                                                                                                                                                                                        图形/transactionalclient_icon.gif

                                                                                                                                                                                                        消息传递系统必然在内部使用事务行为。对于外部客户端来说,能够控制影响其行为的事务范围可能很有用。

                                                                                                                                                                                                        A mes­saging system, by ne­ces­sity, uses trans­ac­tional be­ha­vior in­tern­ally. It may be useful for an ex­ternal client to be able to con­trol the scope of the trans­ac­tions that impact its be­ha­vior.

                                                                                                                                                                                                        客户端如何通过消息系统控制其交易?

                                                                                                                                                                                                        How can a client con­trol its trans­ac­tions with the mes­saging system?



                                                                                                                                                                                                        消息传递系统必须在内部使用事务。单个消息通道可以有多个发送者和多个接收者,因此消息系统必须协调消息以确保发送者不会覆盖彼此的消息,多个点对点通道接收者不会收到相同的消息,多个发布-订阅频道每个接收者都会收到每条消息的一份副本,依此类推。为了管理所有这些,消息传递系统在内部使用事务来确保消息被添加或不添加到通道中,以及从通道中读取或不读取消息。消息传递系统还必须采用事务(最好是两阶段分布式事务)将消息从发送者的计算机复制到接收者的计算机,以便在任何给定时间,该消息“真正”仅在一台计算机或另一台计算机上。

                                                                                                                                                                                                        A mes­saging system must use trans­ac­tions in­tern­ally. A single Mes­sage Chan­nel can have mul­tiple senders and mul­tiple re­ceiv­ers, so the mes­saging system must co­ordin­ate the mes­sages to make sure senders don't over­write each other's Mes­sages, mul­tiple Point-to-Point Chan­nel re­ceiv­ers don't re­ceive the same mes­sage, mul­tiple Pub­lish-Sub­scribe Chan­nel re­ceiv­ers each re­ceive one copy of each mes­sage, and so on. To manage all of this, mes­saging sys­tems in­tern­ally use trans­ac­tions to make sure a mes­sage gets added or doesn't get added to the chan­nel, and gets read or doesn't get read from the chan­nel. Mes­saging sys­tems also have to employ trans­ac­tion­sprefer­ably two-phase, dis­trib­uted trans­ac­tion­sto copy a mes­sage from the sender's com­puter to the re­ceiver's com­puter, such that at any given time, the mes­sage is "really" only on one com­puter or the other.

                                                                                                                                                                                                        发送和接收消息的消息端点是事务性的,即使它们没有意识到这一点。将消息添加到通道的发送方法在事务中执行此操作,以将该消息与同时添加到该通道或从该通道删除的任何其他消息隔离。同样,接收方法也使用事务,这可以防止其他点对点接收方获取相同的消息,甚至确保发布-订阅接收方不会两次读取相同的消息。

                                                                                                                                                                                                        Mes­sage En­d­points send­ing and re­ceiv­ing mes­sages are trans­ac­tional, even if they don't real­ize it. The send method that adds a mes­sage to a chan­nel does so within a trans­ac­tion to isol­ate that mes­sage from any other mes­sages sim­ul­tan­eously being added to or re­moved from that chan­nel. Like­wise, a re­ceive method also uses a trans­ac­tion, which pre­vents other point-to-point re­ceiv­ers from get­ting the same mes­sage and even as­sures that a pub­lish-sub­scribe re­ceiver won't read the same mes­sage twice.

                                                                                                                                                                                                        事务通常被描​​述为 ACID:原子、一致、隔离和持久。只有保证消息传递的事务才是持久的,并且根据定义,消息是原子的。但所有消息传递事务都必须一致且隔离。消息不能存在于通道中——无论是存在还是不存在。此外,应用程序的消息发送和接收必须与可能通过同一通道发送和接收消息的任何其他线程和应用程序隔离。

                                                                                                                                                                                                        Trans­ac­tions are often de­scribed as being ACID: atomic, con­sist­ent, isol­ated, and dur­able. Only trans­ac­tions for Guar­an­teed Mes­saging are dur­able, and a mes­sage by defin­i­tion is atomic. But all mes­saging trans­ac­tions have to be con­sist­ent and isol­ated. A mes­sage can't be sort of in the chan­nelit either is or isn't. Also, an ap­plic­a­tion's send­ing and re­ceiv­ing of mes­sages has to be isol­ated from whatever other threads and ap­plic­a­tions might be send­ing and re­ceiv­ing mes­sages via the same chan­nel.

                                                                                                                                                                                                        对于只想发送或接收单个消息的客户端来说,消息传递系统的内部事务足够且方便。然而,应用程序可能需要更广泛的事务来协调多个消息或协调与其他资源的消息传递。像这样的常见场景包括

                                                                                                                                                                                                        The mes­saging system's in­ternal trans­ac­tions are suf­fi­cient and con­veni­ent for a client that simply wants to send or re­ceive a single mes­sage. How­ever, an ap­plic­a­tion may need a broader trans­ac­tion to co­ordin­ate sev­eral mes­sages or to co­ordin­ate mes­saging with other re­sources. Common scen­arios like this in­clude

                                                                                                                                                                                                        • 发送-接收消息对 接收一条消息并发送另一条消息,例如请求-答复场景或实现消息过滤器(例如消息路由器消息转换器)时

                                                                                                                                                                                                        • Send-Re­ceive Mes­sage Pairs Re­ceive one mes­sage and send an­other, such as a Re­quest-Reply scen­ario or when im­ple­ment­ing a mes­sage filter such as a Mes­sage Router or Mes­sage Trans­lator.

                                                                                                                                                                                                        • 消息组 发送或接收一组相关消息,例如消息序列

                                                                                                                                                                                                        • Mes­sage Groups Send or re­ceive a group of re­lated mes­sages, such as a Mes­sage Se­quence.

                                                                                                                                                                                                        • 消息/数据库协调 将发送或接收消息与更新数据库相 结合,例如使用通道适配器。例如,当应用程序接收并处理订购产品的消息时,应用程序还需要更新产品库存数据库。同样,文档消息的发送者可能希望删除持久化的文档,但前提是该文档发送成功;接收方可能希望在消息真正被视为已被使用之前保留该文档。

                                                                                                                                                                                                        • Mes­sage/Data­base Co­ordin­a­tion Com­bine send­ing or re­ceiv­ing a mes­sage with up­dat­ing a data­base, such as with a Chan­nel Ad­apter. For ex­ample, when an ap­plic­a­tion re­ceives and pro­cesses a mes­sage for or­der­ing a product, the ap­plic­a­tion will also need to update the product in­vent­ory data­base. Like­wise, the sender of a Doc­u­ment Mes­sage may wish to delete a per­sisted doc­u­ment, but only when it is sent suc­cess­fully; the re­ceiver may want to per­sist the doc­u­ment before the mes­sage is truly con­sidered to be con­sumed.

                                                                                                                                                                                                        • 消息/工作流协调 使用一请求-答复消息来执行工作项,并使用事务来确保除非同时发送请求,否则不会获取工作项,并且除非同时发送请求,否则工作项不会完成或中止也收到回复。

                                                                                                                                                                                                        • Mes­sage/Work­flow Co­ordin­a­tion Use a pair of Re­quest-Reply mes­sages to per­form a work item, and use trans­ac­tions to ensure that the work item isn't ac­quired unless the re­quest is also sent, and the work item isn't com­pleted or abor­ted unless the reply is also re­ceived.

                                                                                                                                                                                                        像这样的场景需要更大的原子事务,该事务不仅仅涉及单个消息,还可能涉及除消息传递系统之外的其他事务存储。需要事务,以便如果场景的一部分起作用(例如接收消息)但另一部分不起作用(例如更新数据库或发送另一条消息),则所有部分都可以回滚,就好像它们从未发生过一样,并且然后应用程序可以重试。

                                                                                                                                                                                                        Scen­arios like these re­quire a larger atomic trans­ac­tion that in­volve more than just a single mes­sage and may in­volve other trans­ac­tional stores be­sides the mes­saging system. A trans­ac­tion is re­quired so that if part of the scen­ario works (re­ceiv­ing the mes­sage, for ex­ample) but an­other part does not (such as up­dat­ing the data­base or send­ing an­other mes­sage), all parts can be rolled back as if they never happened, and then the ap­plic­a­tion can try again.

                                                                                                                                                                                                        然而,消息传递系统的内部事务模型不足以允许应用程序协调处理消息与其他消息或其他资源。应用程序需要一种从外部控制消息传递系统的事务并将它们与消息传递系统或其他地方的其他事务组合的方法。

                                                                                                                                                                                                        Yet a mes­saging system's in­ternal trans­ac­tion model is in­suf­fi­cient to allow an ap­plic­a­tion to co­ordin­ate hand­ling a mes­sage with other mes­sages or other re­sources. What is needed is a way for the ap­plic­a­tion to ex­tern­ally con­trol the mes­saging system's trans­ac­tions and com­bine them with other trans­ac­tions in the mes­saging system or else­where.

                                                                                                                                                                                                        使用 事务性客户端使 客户端与消息传递系统的会话成为事务性的,以便客户端可以指定事务边界。

                                                                                                                                                                                                        Use a Trans­ac­tional Client make the client's ses­sion with the mes­saging system trans­ac­tional so that the client can spe­cify trans­ac­tion bound­ar­ies.

                                                                                                                                                                                                        图形/10inf09.gif



                                                                                                                                                                                                        发送者和接收者都可以是事务性的。对于发送者,在发送者提交事务之前,消息实际上不会添加到通道中。对于接收者,在接收者提交事务之前,消息实际上不会从通道中删除。使用显式事务的发送方可以与使用隐式事务的接收方一起使用,反之亦然。单个通道可能具有隐式和显式事务发送者的组合;它还可以具有接收器的组合。

                                                                                                                                                                                                        Both a sender and a re­ceiver can be trans­ac­tional. With a sender, the mes­sage isn't ac­tu­ally added to the chan­nel until the sender com­mits the trans­ac­tion. With a re­ceiver, the mes­sage isn't ac­tu­ally re­moved from the chan­nel until the re­ceiver com­mits the trans­ac­tion. A sender that uses ex­pli­cit trans­ac­tions can be used with a re­ceiver that uses im­pli­cit trans­ac­tions, and vice versa. A single chan­nel might have a com­bin­a­tion of im­pli­citly and ex­pli­citly trans­ac­tional senders; it could also have a com­bin­a­tion of re­ceiv­ers.

                                                                                                                                                                                                        事务接收器序列

                                                                                                                                                                                                        Trans­ac­tional Re­ceiver Se­quence

                                                                                                                                                                                                        图形/10inf10.gif

                                                                                                                                                                                                        使用事务接收器,应用程序可以接收消息,而无需实际从队列中删除消息。此时,如果应用程序崩溃,当它恢复时,消息仍然会在队列中;消息不会丢失。收到消息后,应用程序可以对其进行处理。一旦应用程序完成消息并且确定想要使用它,应用程序就会提交事务,该事务(如果成功)会从通道中删除消息。此时,如果应用程序崩溃,当它恢复时,消息将不再在通道上,因此应用程序最好真正完成消息处理。

                                                                                                                                                                                                        With a trans­ac­tional re­ceiver, an ap­plic­a­tion can re­ceive a mes­sage without ac­tu­ally re­mov­ing the mes­sage from the queue. At this point, if the ap­plic­a­tion crashed, when it re­covered, the mes­sage would still be on the queue; the mes­sage would not be lost. Having re­ceived the mes­sage, the ap­plic­a­tion can then pro­cess it. Once the ap­plic­a­tion is fin­ished with the mes­sage and is cer­tain it wants to con­sume it, the ap­plic­a­tion com­mits the trans­ac­tion, which (if suc­cess­ful) re­moves the mes­sage from the chan­nel. At this point, if the ap­plic­a­tion crashed, when it re­covered, the mes­sage would no longer be on the chan­nel, so the ap­plic­a­tion had better truly be fin­ished with the mes­sage.

                                                                                                                                                                                                        从外部控制消息系统的事务如何帮助应用程序协调多个任务?以下是应用程序在前面描述的场景中将执行的操作:

                                                                                                                                                                                                        How does con­trolling a mes­saging system's trans­ac­tions ex­tern­ally help an ap­plic­a­tion co­ordin­ate sev­eral tasks? Here's what the ap­plic­a­tion would do in the scen­arios de­scribed earlier:

                                                                                                                                                                                                        发送-接收消息对

                                                                                                                                                                                                        Send-Re­ceive Mes­sage Pairs

                                                                                                                                                                                                        1. 要做什么: 启动事务,接收并处理第一条消息,创建并发送第二条消息,然后提交。(此行为通常作为Request-ReplyMessage RouterMessage Translator的一部分实现。 )

                                                                                                                                                                                                        2. What to do: Start a trans­ac­tion, re­ceive and pro­cess the first mes­sage, create and send the second mes­sage, then commit. (This be­ha­vior is often im­ple­men­ted as part of Re­quest-Reply, Mes­sage Router, and Mes­sage Trans­lator.)

                                                                                                                                                                                                        3. 作用: 这可以防止第一条消息从其通道中删除,直到第二条消息成功添加到其通道中。

                                                                                                                                                                                                        4. What this does: This keeps the first mes­sage from being re­moved from its chan­nel until the second mes­sage is suc­cess­fully added to its chan­nel.

                                                                                                                                                                                                        5. 交易类型: 如果两条消息通过同一消息系统中的通道发送,则包含这两个通道的交易是简单交易。但是,如果这两个通道由两个独立的消息传递系统(例如消息桥)管理,则事务将是协调两个消息传递系统的分布式事务。

                                                                                                                                                                                                        6. Trans­ac­tion type: If the two mes­sages are sent via chan­nels in the same mes­saging system, the trans­ac­tion en­com­passing the two chan­nels is a simple one. How­ever, if the two chan­nels are man­aged by two sep­ar­ate mes­saging sys­tems, such as with a Mes­saging Bridge, the trans­ac­tion will be a dis­trib­uted one co­ordin­at­ing the two mes­saging sys­tems.

                                                                                                                                                                                                        7. 警告: 单个事务仅适用于发送回复的请求的接收者。请求的发送者不能使用单个事务来发送请求并等待其回复。如果尝试这样做,请求将永远不会真正发送,因为发送事务未提交,因此永远不会收到回复。

                                                                                                                                                                                                        8. Warn­ing: A single trans­ac­tion only works for the re­ceiver of a re­quest send­ing a reply. The sender of a re­quest cannot use a single trans­ac­tion to send a re­quest and wait for its reply. If it tries to do this, the re­quest will never really be sent­be­cause the send trans­ac­tion isn't com­mit­tedso the reply will never be re­ceived.

                                                                                                                                                                                                        消息组

                                                                                                                                                                                                        Mes­sage Groups

                                                                                                                                                                                                        1. 要做什么: 启动事务,发送或接收组中的所有消息(例如消息序列) ,然后提交。

                                                                                                                                                                                                        2. What to do: Start a trans­ac­tion, send or re­ceive all of the mes­sages in the group (such as a Mes­sage Se­quence ), then commit.

                                                                                                                                                                                                        3. 作用: 发送时,组中的任何消息都不会添加到通道中,直到全部发送成功。接收时,在所有消息都收到之前,不会从通道中删除任何消息。

                                                                                                                                                                                                        4. What this does: When send­ing, none of the mes­sages in the group will be added to the chan­nel until they are all suc­cess­fully sent. When re­ceiv­ing, none of the mes­sages will be re­moved from the chan­nel until all are re­ceived.

                                                                                                                                                                                                        5. 事务类型: 由于所有消息都发送到单个通道或从单个通道接收,因此该通道将由单个消息系统管理,因此事务将是一个简单的事务。此外,在许多消息传递系统实现中,在单个事务中发送一组消息可确保它们将按照发送顺序在通道的另一端接收。

                                                                                                                                                                                                        6. Trans­ac­tion type: Since all of the mes­sages are being sent to or re­ceived from a single chan­nel, that chan­nel will be man­aged by a single mes­saging system, so the trans­ac­tion will be a simple one. Also, in many mes­saging system im­ple­ment­a­tions, send­ing a group of mes­sages in a single trans­ac­tion en­sures that they will be re­ceived on the other end of the chan­nel in the order they were sent.

                                                                                                                                                                                                        消息/数据库协调

                                                                                                                                                                                                        Mes­sage/Data­base Co­ordin­a­tion

                                                                                                                                                                                                        1. 要做什么: 启动事务,接收消息,更新数据库,然后提交。或者,更新数据库并发送消息向其他人报告更新,然后提交。(此行为通常由 Channel Adapter 实现

                                                                                                                                                                                                        2. What to do: Start a trans­ac­tion, re­ceive a mes­sage, update the data­base, and then commit. Or, update the data­base and send a mes­sage to report the update to others, and then commit. (This be­ha­vior is often im­ple­men­ted by a Chan­nel Ad­apter.)

                                                                                                                                                                                                        3. 作用: 除非更新数据库,否则消息不会被删除(或者如果无法发送消息,则数据库更改将不会保留)。

                                                                                                                                                                                                        4. What this does: The mes­sage will not be re­moved unless the data­base is up­dated (or the data­base change will not stick if the mes­sage cannot be sent).

                                                                                                                                                                                                        5. 事务类型: 由于消息系统和数据库都有自己的事务管理器,因此协调它们的事务将是分布式事务。

                                                                                                                                                                                                        6. Trans­ac­tion type: Since the mes­saging system and the data­base each has its own trans­ac­tion man­ager, the trans­ac­tion to co­ordin­ate them will be a dis­trib­uted one.

                                                                                                                                                                                                        消息/工作流程协调

                                                                                                                                                                                                        Mes­sage/Work­flow Co­ordin­a­tion

                                                                                                                                                                                                        1. 要做什么: 使用一对请求-答复消息来执行工作项。启动事务,获取工作项,发送请求消息,然后提交。或者,启动另一个事务,接收回复消息,完成或中止工作项,然后提交。

                                                                                                                                                                                                        2. What to do: Use a pair of Re­quest-Reply mes­sages to per­form a work item. Start a trans­ac­tion, ac­quire the work item, send the re­quest mes­sage, and then commit. Or, start an­other trans­ac­tion, re­ceive the reply mes­sage, com­plete or abort the work item, then commit.

                                                                                                                                                                                                        3. 作用: 除非发送请求,否则工作项不会被提交;除非工作项更新,否则回复不会被删除。

                                                                                                                                                                                                        4. What this does: The work item will not be com­mit­ted unless the re­quest is sent; the reply will not be re­moved unless the work item is up­dated.

                                                                                                                                                                                                        5. 事务类型: 由于消息系统和工作流引擎都有自己的事务管理器,因此协调它们的事务将是分布式事务。

                                                                                                                                                                                                        6. Trans­ac­tion type: Since the mes­saging system and the work­flow engine each has its own trans­ac­tion man­ager, the trans­ac­tion to co­ordin­ate them will be a dis­trib­uted one.

                                                                                                                                                                                                        通过这种方式,应用程序可以确保它不会丢失收到的消息或忘记发送它应该发送的消息。如果中间出现问题,应用程序可以回滚事务并重试。

                                                                                                                                                                                                        In this way, the ap­plic­a­tion can assure that it will not lose the mes­sages it re­ceives or forget to send a mes­sage that it should. If some­thing goes wrong in the middle, the ap­plic­a­tion can roll back the trans­ac­tion and try again.

                                                                                                                                                                                                        使用事件驱动消费者的事务客户端可能无法按预期工作。消费者通常必须在将消息传递给应用程序之前提交用于接收消息的事务。然后,如果应用程序检查消息并决定不想使用它,或者如果应用程序遇到错误并想要回滚消费操作,则它不能,因为它无权访问事务。因此,无论客户端是否是事务性的,事件驱动的消费者都倾向于以相同的方式工作。

                                                                                                                                                                                                        Trans­ac­tional cli­ents using Event-Driven Con­sumers may not work as ex­pec­ted. The con­sumer typ­ic­ally must commit the trans­ac­tion for re­ceiv­ing the mes­sage before passing the mes­sage to the ap­plic­a­tion. Then, if the ap­plic­a­tion ex­am­ines the mes­sage and de­cides it does not want to con­sume it, or if the ap­plic­a­tion en­coun­ters an error and wants to roll back the con­sume action, it cannot be­cause it does not have access to the trans­ac­tion. So, an event-driven con­sumer tends to work the same whether or not its client is trans­ac­tional.

                                                                                                                                                                                                        消息系统能够参与分布式事务,尽管某些实现可能不支持它。在 JMS 中,提供者可以充当 XA 资源并参与 Java Transaction API [ JTA ] 事务。此行为由javax.jms包(特别是javax.jms.XASession )中的 XA 类以及javax.transaction.xa包定义。JMS 规范建议 JMS 客户端不要尝试直接处理分布式事务,因此应用程序应该使用 J2EE 应用程序服务器提供的分布式事务支持。MSMQ还可以参与XA事务;此行为在 .NET 中由MessageQueue.Transactional属性和MessageQueueTransaction 类

                                                                                                                                                                                                        Mes­saging sys­tems are cap­able of par­ti­cip­at­ing in a dis­trib­uted trans­ac­tion, al­though some im­ple­ment­a­tions may not sup­port it. In JMS, a pro­vider can act as an XA re­source and par­ti­cip­ate in Java Trans­ac­tion API [JTA] trans­ac­tions. This be­ha­vior is defined by the XA classes in the javax.jms pack­age, par­tic­u­larly javax.jms.XASes­sion, and by the javax.trans­ac­tion.xa pack­age. The JMS spe­cific­a­tion re­com­mends that JMS cli­ents not try to handle dis­trib­uted trans­ac­tions dir­ectly, so an ap­plic­a­tion should use the dis­trib­uted trans­ac­tion sup­port provided by a J2EE ap­plic­a­tion server. MSMQ can also par­ti­cip­ate in an XA trans­ac­tion; this be­ha­vior is ex­posed in .NET by the Mes­sageQueue.Trans­ac­tional prop­erty and the Mes­sageQueueTrans­ac­tion class.

                                                                                                                                                                                                        如前所述,事务客户端可用作其他模式的一部分,例如请求-答复、管道和过滤器中的消息过滤器、消息序列和通道适配器。同样,事件消息的接收者可能希望在从通道中完全删除其消息之前完成事件的处理。然而,事务性客户端不能与事件驱动的消费者或消息调度器一起很好地工作,可能会给竞争,但与单个轮询消费者一起工作却很好

                                                                                                                                                                                                        As dis­cussed earlier, Trans­ac­tional Cli­ents can be useful as part of other pat­terns, such as Re­quest-Reply, mes­sage fil­ters in Pipes and Fil­ters, Mes­sage Se­quence, and Chan­nel Ad­apter. Like­wise, the re­ceiver of an Event Mes­sage may want to com­plete pro­cess­ing the event before re­mov­ing its mes­sage from the chan­nel com­pletely. How­ever, Trans­ac­tional Clients do not work well with Event-Driven Con­sumers or Mes­sage Dis­patch­ers, can cause prob­lems for Com­pet­ing Con­sumers, but work well with a single Polling Con­sumer.

                                                                                                                                                                                                        示例: JMS 事务处理会话

                                                                                                                                                                                                        Ex­ample: JMS Trans­acted Ses­sion

                                                                                                                                                                                                        在 JMS 中,客户端在创建其会话时使其自身具有事务性 [ JMS 1.1 ]、[ Hapner ]。

                                                                                                                                                                                                        In JMS, a client makes itself trans­ac­tional when it cre­ates its ses­sion [JMS 1.1], [Hapner].

                                                                                                                                                                                                        Connection 连接 = // 获取连接
                                                                                                                                                                                                        会话会话=
                                                                                                                                                                                                            连接.createSession(true, Session.AUTO_ACKNOWLEDGE);
                                                                                                                                                                                                        
                                                                                                                                                                                                        Con­nec­tion con­nec­tion = // Get the con­nec­tion
                                                                                                                                                                                                        Ses­sion ses­sion =
                                                                                                                                                                                                            con­nec­tion.cre­ateSes­sion(true, Ses­sion.AUTO_AC­KNOW­LEDGE);
                                                                                                                                                                                                        

                                                                                                                                                                                                        此会话是事务性的,因为第一个createSession参数设置为 true 。

                                                                                                                                                                                                        This ses­sion is trans­ac­tional be­cause the first cre­ateSes­sion para­meter is set to true.

                                                                                                                                                                                                        当客户端使用事务会话时,它必须显式提交发送和接收以使它们真实。

                                                                                                                                                                                                        When a client is using a trans­ac­tional ses­sion, it must ex­pli­citly commit sends and re­ceives to make them real.

                                                                                                                                                                                                        Queue队列= //获取队列
                                                                                                                                                                                                        MessageConsumer 消费者 = session.createConsumer(queue);
                                                                                                                                                                                                        消息消息=consumer.receive();
                                                                                                                                                                                                        
                                                                                                                                                                                                        Queue queue = // Get the queue
                                                                                                                                                                                                        Mes­sage­Con­sumer con­sumer = ses­sion.cre­ate­Con­sumer(queue);
                                                                                                                                                                                                        Mes­sage mes­sage = con­sumer.re­ceive();
                                                                                                                                                                                                        

                                                                                                                                                                                                        此时,消息仅在消费者的事务视图中被消费。但对于其他拥有自己事务观点的消费者来说,该消息仍然可用。

                                                                                                                                                                                                        At this point, the mes­sage has only been con­sumed in the con­sumer's trans­ac­tional view. But to other con­sumers with their own trans­ac­tional views, the mes­sage is still avail­able.

                                                                                                                                                                                                        会话.commit();
                                                                                                                                                                                                        
                                                                                                                                                                                                        ses­sion.commit();
                                                                                                                                                                                                        

                                                                                                                                                                                                        现在,假设提交消息不会引发任何异常,消费者的事务视图将成为消息系统的视图,消息系统现在会考虑所消费的消息。

                                                                                                                                                                                                        Now, as­sum­ing that the commit mes­sage does not throw any ex­cep­tions, the con­sumer's trans­ac­tional view be­comes the mes­sage system's, which now con­siders the mes­sage con­sumed.



                                                                                                                                                                                                        示例: .NET 事务队列

                                                                                                                                                                                                        Ex­ample: .NET Trans­ac­tional Queue

                                                                                                                                                                                                        在 .NET 中,默认情况下队列不是事务性的,因此要使用事务性客户端,必须在创建队列时将其设置为事务性的:

                                                                                                                                                                                                        In .NET, queues are not trans­ac­tional by de­fault, so to use a trans­ac­tional client, the queue must be made trans­ac­tional when it is cre­ated:

                                                                                                                                                                                                        MessageQueue.Create("MyQ​​ueue", true);
                                                                                                                                                                                                        
                                                                                                                                                                                                        Mes­sageQueue.Create("MyQueue", true);
                                                                                                                                                                                                        

                                                                                                                                                                                                        一旦队列是事务性的,队列上的每个客户端操作(发送或接收)就可以是事务性的或非事务性的。事务性接收看起来像这样:

                                                                                                                                                                                                        Once a queue is trans­ac­tional, each client action (send or re­ceive) on the queue can be trans­ac­tional or non­trans­ac­tional. A trans­ac­tional Re­ceive looks like this:

                                                                                                                                                                                                        MessageQueue 队列 = new MessageQueue("MyQ​​ueue");
                                                                                                                                                                                                        MessageQueueTransaction 事务 =
                                                                                                                                                                                                            新的消息队列事务();
                                                                                                                                                                                                        交易.Begin();
                                                                                                                                                                                                        消息消息=queue.Receive(transaction);
                                                                                                                                                                                                        事务.Commit();
                                                                                                                                                                                                        
                                                                                                                                                                                                        Mes­sageQueue queue = new Mes­sageQueue("MyQueue");
                                                                                                                                                                                                        Mes­sageQueueTrans­ac­tion trans­ac­tion =
                                                                                                                                                                                                            new Mes­sageQueueTrans­ac­tion();
                                                                                                                                                                                                        trans­ac­tion.Begin();
                                                                                                                                                                                                        Mes­sage mes­sage = queue.Re­ceive(trans­ac­tion);
                                                                                                                                                                                                        trans­ac­tion.Commit();
                                                                                                                                                                                                        

                                                                                                                                                                                                        尽管客户端已收到消息,但消息系统并未使该消息在队列上不可用,直到客户端成功提交事务[ SysMsg ]。

                                                                                                                                                                                                        Al­though the client had re­ceived the mes­sage, the mes­saging system did not make the mes­sage un­avail­able on the queue until the client com­mit­ted the trans­ac­tion suc­cess­fully [SysMsg].



                                                                                                                                                                                                        示例: 使用 MSMQ 的事务过滤器

                                                                                                                                                                                                        Ex­ample: Trans­ac­tional Filter with MSMQ

                                                                                                                                                                                                        以下示例增强了管道和过滤器中引入的基本过滤器组件以使用事务。此示例实现了发送-接收消息对场景,在同一事务内接收和发送消息。我们实际上只需添加几行代码即可使过滤器具有事务性。我们使用 MessageQueueTransaction 类型的变量管理事务。我们在消费输入消息之前打开一个事务并在我们发布输出消息后提交。如果发生任何异常,我们将中止事务,这将回滚所有消息消费和发布操作,并将输入消息返回到队列以供其他队列使用者使用。

                                                                                                                                                                                                        The fol­low­ing ex­ample en­hances the basic filter com­pon­ent in­tro­duced in Pipes and Fil­ters to use trans­ac­tions. This ex­ample im­ple­ments the Send-Re­ceive Mes­sage Pair scen­ario, re­ceiv­ing and send­ing a mes­sage inside the same trans­ac­tion. We really have to add only a few lines of code to make the filter trans­ac­tional. We use a vari­able of type Mes­sageQueueTrans­ac­tion to manage the trans­ac­tion. We open a trans­ac­tion before we con­sume the input mes­sage and commit after we pub­lish the output mes­sage. If any ex­cep­tion occurs, we abort the trans­ac­tion, which rolls back all mes­sage con­sump­tion and pub­lic­a­tion ac­tions and re­turns the input mes­sage to the queue to be avail­able to other queue con­sumers.

                                                                                                                                                                                                        
                                                                                                                                                                                                        公共类事务过滤器
                                                                                                                                                                                                        {
                                                                                                                                                                                                            受保护的消息队列输入队列;
                                                                                                                                                                                                            受保护的消息队列输出队列;
                                                                                                                                                                                                        
                                                                                                                                                                                                            受保护的线程接收线程;
                                                                                                                                                                                                            受保护的布尔stopFlag = false;
                                                                                                                                                                                                        
                                                                                                                                                                                                            公共事务过滤器(消息队列输入队列,
                                                                                                                                                                                                        图形/ccc.gif消息队列(输出队列)
                                                                                                                                                                                                            {
                                                                                                                                                                                                                this.inputQueue = inputQueue;
                                                                                                                                                                                                                this.inputQueue.Formatter = 新的 System.Messaging
                                                                                                                                                                                                        图形/ccc.gif.XmlMessageFormatter
                                                                                                                                                                                                                                              (new String[] {"系统
                                                                                                                                                                                                        图形/ccc.gif.字符串,mscorlib"});
                                                                                                                                                                                                                this.outputQueue = 输出队列;
                                                                                                                                                                                                            }
                                                                                                                                                                                                        
                                                                                                                                                                                                            公共无效进程()
                                                                                                                                                                                                            {
                                                                                                                                                                                                                ThreadStart receiveDelegate = new ThreadStart(this
                                                                                                                                                                                                        图形/ccc.gif.接收消息);
                                                                                                                                                                                                                receiveThread = 新线程(receiveDelegate);
                                                                                                                                                                                                                receiveThread.Start();
                                                                                                                                                                                                            }
                                                                                                                                                                                                        
                                                                                                                                                                                                            私有无效接收消息()
                                                                                                                                                                                                            {
                                                                                                                                                                                                                MessageQueueTransaction myTransaction = 新
                                                                                                                                                                                                        图形/ccc.gif消息队列事务();
                                                                                                                                                                                                        
                                                                                                                                                                                                                而(!stopFlag)
                                                                                                                                                                                                                {
                                                                                                                                                                                                                    尝试
                                                                                                                                                                                                                    {
                                                                                                                                                                                                                        myTransaction.Begin();
                                                                                                                                                                                                                        消息输入消息 = inputQueue.Receive
                                                                                                                                                                                                        图形/ccc.gif(我的交易);
                                                                                                                                                                                                                        消息输出消息 = ProcessMessage(inputMessage);
                                                                                                                                                                                                                        输出队列.Send(outputMessage, myTransaction);
                                                                                                                                                                                                                        myTransaction.Commit();
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                    捕获(异常 e)
                                                                                                                                                                                                                    {
                                                                                                                                                                                                                        Console.WriteLine(e.Message + " - 事务
                                                                                                                                                                                                        图形/ccc.gif中止”);
                                                                                                                                                                                                                        myTransaction.Abort();
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                }
                                                                                                                                                                                                            }
                                                                                                                                                                                                        
                                                                                                                                                                                                        
                                                                                                                                                                                                            受保护的虚拟消息ProcessMessage(Message m)
                                                                                                                                                                                                            {
                                                                                                                                                                                                                Console.WriteLine("收到消息:" + m.Body);
                                                                                                                                                                                                                返回米;
                                                                                                                                                                                                            }
                                                                                                                                                                                                        }
                                                                                                                                                                                                        
                                                                                                                                                                                                        
                                                                                                                                                                                                        public class Trans­ac­tion­al­Fil­ter
                                                                                                                                                                                                        {
                                                                                                                                                                                                            pro­tec­ted Mes­sageQueue in­putQueue;
                                                                                                                                                                                                            pro­tec­ted Mes­sageQueue out­putQueue;
                                                                                                                                                                                                        
                                                                                                                                                                                                            pro­tec­ted Thread re­ceiv­eThread;
                                                                                                                                                                                                            pro­tec­ted bool stop­Flag = false;
                                                                                                                                                                                                        
                                                                                                                                                                                                            public Trans­ac­tion­al­Fil­ter (Mes­sageQueue in­putQueue,
                                                                                                                                                                                                         Mes­sageQueue out­putQueue)
                                                                                                                                                                                                            {
                                                                                                                                                                                                                this.in­putQueue = in­putQueue;
                                                                                                                                                                                                                this.in­putQueue.Format­ter = new System.Mes­saging
                                                                                                                                                                                                        .Xm­lMes­sage­Format­ter
                                                                                                                                                                                                                                              (new String[] {"System
                                                                                                                                                                                                        .String,mscorlib"});
                                                                                                                                                                                                                this.out­putQueue = out­putQueue;
                                                                                                                                                                                                            }
                                                                                                                                                                                                        
                                                                                                                                                                                                            public void Pro­cess()
                                                                                                                                                                                                            {
                                                                                                                                                                                                                Thread­Start re­ceiveDeleg­ate = new Thread­Start(this
                                                                                                                                                                                                        .Re­ceiveMes­sages);
                                                                                                                                                                                                                re­ceiv­eThread = new Thread(re­ceiveDeleg­ate);
                                                                                                                                                                                                                re­ceiv­eThread.Start();
                                                                                                                                                                                                            }
                                                                                                                                                                                                        
                                                                                                                                                                                                            private void Re­ceiveMes­sages()
                                                                                                                                                                                                            {
                                                                                                                                                                                                                Mes­sageQueueTrans­ac­tion myTrans­ac­tion = new
                                                                                                                                                                                                         Mes­sageQueueTrans­ac­tion();
                                                                                                                                                                                                        
                                                                                                                                                                                                                while (!stop­Flag)
                                                                                                                                                                                                                {
                                                                                                                                                                                                                    try
                                                                                                                                                                                                                    {
                                                                                                                                                                                                                        myTrans­ac­tion.Begin();
                                                                                                                                                                                                                        Mes­sage in­put­Mes­sage = in­putQueue.Re­ceive
                                                                                                                                                                                                        (myTrans­ac­tion);
                                                                                                                                                                                                                        Mes­sage out­put­Mes­sage = Pro­cess­Mes­sage(in­put­Mes­sage);
                                                                                                                                                                                                                        out­putQueue.Send(out­put­Mes­sage, myTrans­ac­tion);
                                                                                                                                                                                                                        myTrans­ac­tion.Commit();
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                    catch (Ex­cep­tion e)
                                                                                                                                                                                                                    {
                                                                                                                                                                                                                        Con­sole.WriteLine(e.Mes­sage + " - Trans­ac­tion
                                                                                                                                                                                                         abor­ted ");
                                                                                                                                                                                                                        myTrans­ac­tion.Abort();
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                }
                                                                                                                                                                                                            }
                                                                                                                                                                                                        
                                                                                                                                                                                                        
                                                                                                                                                                                                            pro­tec­ted vir­tual Mes­sage Pro­cess­Mes­sage(Mes­sage m)
                                                                                                                                                                                                            {
                                                                                                                                                                                                                Con­sole.WriteLine("Re­ceived Mes­sage: " + m.Body);
                                                                                                                                                                                                                return m;
                                                                                                                                                                                                            }
                                                                                                                                                                                                        }
                                                                                                                                                                                                        

                                                                                                                                                                                                        我们如何验证我们的交易客户端是否按预期工作?我们创建基本TransactionalFilter 类的子类,恰当地命名为RandomlyFailingFilter。对于每条消费的消息,此过滤器都会抽取一个 0 到 10 之间的随机数。如果该数字小于 3,它会抛出任意异常(对于示例来说,ArgumentNullException 似乎足够方便)。如果我们在管道和过滤器中描述的基本非事务过滤器之上实现此过滤器,我们将丢失大约三分之一的消息。

                                                                                                                                                                                                        How do we verify that our Trans­ac­tional Client works as in­ten­ded? We create a sub­class of the basic Trans­ac­tion­al­Fil­ter class, the aptly named class Ran­domly­Fail­ing­Fil­ter. For each con­sumed mes­sage, this filter draws a random number between 0 and 10. If the number is less than 3, it throws an ar­bit­rary ex­cep­tion (Ar­gu­ment­NullEx­cep­tion seemed con­veni­ent enough for an ex­ample). If we im­ple­men­ted this filter on top of our basic, non­trans­ac­tional filter de­scribed in Pipes and Fil­ters, we would lose about one in three mes­sages.

                                                                                                                                                                                                        
                                                                                                                                                                                                        公共类RandomlyFailingFilter:TransactionalFilter
                                                                                                                                                                                                        {
                                                                                                                                                                                                            随机兰特 = new Random();
                                                                                                                                                                                                        
                                                                                                                                                                                                            公共RandomlyFailingFilter(消息队列输入队列,
                                                                                                                                                                                                        图形/ccc.gif消息队列(输出队列)
                                                                                                                                                                                                              :基(输入队列,输出队列){}
                                                                                                                                                                                                        
                                                                                                                                                                                                            受保护覆盖​​消息ProcessMessage(消息m)
                                                                                                                                                                                                            {
                                                                                                                                                                                                                字符串文本 = (字符串)m.Body;
                                                                                                                                                                                                                Console.WriteLine("收到消息:" + text);
                                                                                                                                                                                                        
                                                                                                                                                                                                                if (rand.Next(10) < 3)
                                                                                                                                                                                                                {
                                                                                                                                                                                                                    Console.WriteLine("异常");
                                                                                                                                                                                                                    抛出(新的ArgumentNullException());
                                                                                                                                                                                                                }
                                                                                                                                                                                                                if (文本==“结束”)
                                                                                                                                                                                                                    停止标志=真;
                                                                                                                                                                                                                返回(米);
                                                                                                                                                                                                            }
                                                                                                                                                                                                        }
                                                                                                                                                                                                        
                                                                                                                                                                                                        
                                                                                                                                                                                                        public class Ran­domly­Fail­ing­Fil­ter : Trans­ac­tion­al­Fil­ter
                                                                                                                                                                                                        {
                                                                                                                                                                                                            Random rand = new Random();
                                                                                                                                                                                                        
                                                                                                                                                                                                            public Ran­domly­Fail­ing­Fil­ter(Mes­sageQueue in­putQueue,
                                                                                                                                                                                                         Mes­sageQueue out­putQueue)
                                                                                                                                                                                                              : base (in­putQueue, out­putQueue) { }
                                                                                                                                                                                                        
                                                                                                                                                                                                            pro­tec­ted over­ride Mes­sage Pro­cess­Mes­sage(Mes­sage m)
                                                                                                                                                                                                            {
                                                                                                                                                                                                                string text = (string)m.Body;
                                                                                                                                                                                                                Con­sole.WriteLine("Re­ceived Mes­sage: " + text);
                                                                                                                                                                                                        
                                                                                                                                                                                                                if (rand.Next(10) < 3)
                                                                                                                                                                                                                {
                                                                                                                                                                                                                    Con­sole.WriteLine("EX­CEP­TION");
                                                                                                                                                                                                                    throw (new Ar­gu­ment­NullEx­cep­tion());
                                                                                                                                                                                                                }
                                                                                                                                                                                                                if (text == "end")
                                                                                                                                                                                                                    stop­Flag = true;
                                                                                                                                                                                                                return(m);
                                                                                                                                                                                                            }
                                                                                                                                                                                                        }
                                                                                                                                                                                                        

                                                                                                                                                                                                        为了确保我们不会丢失事务版本的任何消息,我们安装了一个简单的测试工具,它将一系列消息发布到输入队列,并确保它可以从输出队列以正确的顺序接收所有消息。重要的是要记住,只有当我们运行事务过滤器的单个实例时,输出消息才会保持顺序。如果我们并行运行多个过滤器,消息可能(并且将会)乱序(请参阅Resequencer )

                                                                                                                                                                                                        To make sure that we do not lose any mes­sages with the trans­ac­tional ver­sion, we rigged up a simple test har­ness that pub­lishes a se­quence of mes­sages to the input queue and makes sure that it can re­ceive all mes­sages in the cor­rect order from the output queue. It is im­port­ant to re­mem­ber that the output mes­sages remain in se­quence only if we run a single in­stance of the trans­ac­tional filter. If we run mul­tiple fil­ters in par­al­lel, mes­sages can (and will) get out of order (see Resequen­cer ).

                                                                                                                                                                                                        
                                                                                                                                                                                                        公共无效运行测试()
                                                                                                                                                                                                        {
                                                                                                                                                                                                            MessageQueueTransaction myTransaction = 新
                                                                                                                                                                                                        图形/ccc.gif消息队列事务();
                                                                                                                                                                                                        
                                                                                                                                                                                                            for (int i=0; i < messages.Length; i++)
                                                                                                                                                                                                            {
                                                                                                                                                                                                                myTransaction.Begin();
                                                                                                                                                                                                                inQueue.Send(messages[i], myTransaction);
                                                                                                                                                                                                                myTransaction.Commit();
                                                                                                                                                                                                            }
                                                                                                                                                                                                        
                                                                                                                                                                                                            for (int i=0; i < messages.Length; i++)
                                                                                                                                                                                                        
                                                                                                                                                                                                            {
                                                                                                                                                                                                                myTransaction.Begin();
                                                                                                                                                                                                                消息消息 = outQueue.Receive(new TimeSpan(0,0,3),
                                                                                                                                                                                                        图形/ccc.gif我的交易);
                                                                                                                                                                                                                myTransaction.Commit();
                                                                                                                                                                                                        
                                                                                                                                                                                                                字符串文本 = (String)message.Body;
                                                                                                                                                                                                                控制台.Write(文本);
                                                                                                                                                                                                                if (文本==消息[i])
                                                                                                                                                                                                                    Console.WriteLine("确定");
                                                                                                                                                                                                                别的
                                                                                                                                                                                                                    Console.WriteLine(" 错误");
                                                                                                                                                                                                            }
                                                                                                                                                                                                        
                                                                                                                                                                                                            Console.WriteLine("按回车键退出");
                                                                                                                                                                                                            Console.ReadLine();
                                                                                                                                                                                                        }
                                                                                                                                                                                                        
                                                                                                                                                                                                        
                                                                                                                                                                                                        public void RunTests()
                                                                                                                                                                                                        {
                                                                                                                                                                                                            Mes­sageQueueTrans­ac­tion myTrans­ac­tion = new
                                                                                                                                                                                                         Mes­sageQueueTrans­ac­tion();
                                                                                                                                                                                                        
                                                                                                                                                                                                            for (int i=0; i < mes­sages.Length; i++)
                                                                                                                                                                                                            {
                                                                                                                                                                                                                myTrans­ac­tion.Begin();
                                                                                                                                                                                                                in­Queue.Send(mes­sages[i], myTrans­ac­tion);
                                                                                                                                                                                                                myTrans­ac­tion.Commit();
                                                                                                                                                                                                            }
                                                                                                                                                                                                        
                                                                                                                                                                                                            for (int i=0; i < mes­sages.Length; i++)
                                                                                                                                                                                                        
                                                                                                                                                                                                            {
                                                                                                                                                                                                                myTrans­ac­tion.Begin();
                                                                                                                                                                                                                Mes­sage mes­sage = out­Queue.Re­ceive(new TimeSpan(0,0,3),
                                                                                                                                                                                                         myTrans­ac­tion);
                                                                                                                                                                                                                myTrans­ac­tion.Commit();
                                                                                                                                                                                                        
                                                                                                                                                                                                                String text = (String)mes­sage.Body;
                                                                                                                                                                                                                Con­sole.Write(text);
                                                                                                                                                                                                                if (text == mes­sages[i])
                                                                                                                                                                                                                    Con­sole.WriteLine(" OK");
                                                                                                                                                                                                                else
                                                                                                                                                                                                                    Con­sole.WriteLine(" ERROR");
                                                                                                                                                                                                            }
                                                                                                                                                                                                        
                                                                                                                                                                                                            Con­sole.WriteLine("Hit enter to exit");
                                                                                                                                                                                                            Con­sole.Read­Line();
                                                                                                                                                                                                        }
                                                                                                                                                                                                        



                                                                                                                                                                                                          轮询消费者

                                                                                                                                                                                                          Polling Consumer

                                                                                                                                                                                                          图形/pollingconsumer_icon.gif

                                                                                                                                                                                                          应用程序需要使用Messages ,但它希望控制何时使用每条消息。

                                                                                                                                                                                                          An ap­plic­a­tion needs to con­sume Mes­sages, but it wants to con­trol when it con­sumes each mes­sage.

                                                                                                                                                                                                          当应用程序准备就绪时,应用程序如何使用消息?

                                                                                                                                                                                                          How can an ap­plic­a­tion con­sume a mes­sage when the ap­plic­a­tion is ready?



                                                                                                                                                                                                          消息消费者退出的原因之一是消费消息。消息代表需要完成的工作,因此消费者需要使用这些消息并完成工作。

                                                                                                                                                                                                          Mes­sage con­sumers exit for one reas­onto con­sume mes­sages. The mes­sages rep­res­ent work that needs to be done, so the con­sumer needs to con­sume those mes­sages and do the work.

                                                                                                                                                                                                          但是消费者如何知道新消息何时可用?最简单的方法是消费者重复检查通道以查看消息是否可用。当消息可用时,它会消耗该消息,然后返回检查下一条消息。这个过程称为轮询

                                                                                                                                                                                                          But how does the con­sumer know when a new mes­sage is avail­able? The easi­est ap­proach is for the con­sumer to re­peatedly check the chan­nel to see if a mes­sage is avail­able. When a mes­sage is avail­able, it con­sumes the mes­sage and then goes back to check­ing for the next one. This pro­cess is called polling.

                                                                                                                                                                                                          轮询的优点在于消费者可以在准备好接收下一条消息时请求下一条消息。因此,它以它想要的速率而不是消息到达通道的速率消耗消息。

                                                                                                                                                                                                          The beauty of polling is that the con­sumer can re­quest the next mes­sage when it is ready for an­other mes­sage. So, it con­sumes mes­sages at the rate it wants to rather than at the rate they arrive in the chan­nel.

                                                                                                                                                                                                          应用程序应该使用轮询消费者,它在想要接收消息时显式进行调用。

                                                                                                                                                                                                          The ap­plic­a­tion should use a Polling Con­sumer, one that ex­pli­citly makes a call when it wants to re­ceive a mes­sage.

                                                                                                                                                                                                          图形/10inf11.gif



                                                                                                                                                                                                          这也称为同步接收器,因为接收器线程会阻塞,直到收到消息。我们将其称为轮询消费者,因为接收者轮询消息,处理它,然后轮询另一个消息。为了方便起见,消息传递 API 通常提供一个接收方法,该方法会一直阻塞,直到消息被传递为止,此外还有receiveNoWait()Receive(0)等在没有可用消息时立即返回的方法。仅当接收方轮询速度快于消息到达速度时,这种差异才会明显。

                                                                                                                                                                                                          This is also known as a syn­chron­ous re­ceiver, be­cause the re­ceiver thread blocks until a mes­sage is re­ceived. We call it a Polling Con­sumer be­cause the re­ceiver polls for a mes­sage, pro­cesses it, then polls for an­other. As a con­veni­ence, mes­saging APIs usu­ally provide a re­ceive method that blocks until a mes­sage is de­livered in ad­di­tion to meth­ods like re­ceiveNoWait() and Re­ceive(0) that return im­me­di­ately if no mes­sage is avail­able. This dif­fer­ence is only ap­par­ent when the re­ceiver is polling faster than mes­sages are ar­riv­ing.

                                                                                                                                                                                                          轮询使用者应用程序用来通过显式请求来接收消息的对象。当应用程序准备好接收另一条消息时,它会轮询消费者,消费者又从消息传递系统获取消息并将其返回。(消费者如何从消息传递系统获取消息是特定于实现的,可能涉及也可能不涉及轮询。应用程序所知道的是,在明确请求消息之前它不会收到消息。)

                                                                                                                                                                                                          A Polling Con­sumer is an object that an ap­plic­a­tion uses to re­ceive mes­sages by ex­pli­citly re­quest­ing them. When the ap­plic­a­tion is ready for an­other mes­sage, it polls the con­sumer, which in turn gets a mes­sage from the mes­saging system and re­turns it. (How the con­sumer gets the mes­sage from the mes­saging system is im­ple­ment­a­tion-spe­cific and may or may not in­volve polling. All the ap­plic­a­tion knows is that it doesn't get the mes­sage until it ex­pli­citly asks for one.)

                                                                                                                                                                                                          轮询消费者序列

                                                                                                                                                                                                          Polling Con­sumer Se­quence

                                                                                                                                                                                                          图形/10inf12.gif

                                                                                                                                                                                                          当应用程序轮询消息时,使用者会阻塞,直到收到要返回的消息(或直到满足某些其他条件,例如时间限制)。应用程序收到消息后,就可以对其进行处理。一旦处理完消息并希望接收另一条消息,应用程序就可以再次轮询。

                                                                                                                                                                                                          When the ap­plic­a­tion polls for a mes­sage, the con­sumer blocks until it gets a mes­sage to return (or until some other con­di­tion is met, such as a time limit). Once the ap­plic­a­tion re­ceives the mes­sage, it can pro­cess it. Once it is through pro­cess­ing the mes­sage and wishes to re­ceive an­other, the ap­plic­a­tion can poll again.

                                                                                                                                                                                                          通过使用轮询消费者,应用程序可以通过限制轮询的线程数量来控制并发消费的消息数量。这有助于防止接收应用程序因过多的请求而不堪重负;额外的消息会排队等待接收者可以处理它们。

                                                                                                                                                                                                          By using Polling Con­sumers, an ap­plic­a­tion can con­trol how many mes­sages are con­sumed con­cur­rently by lim­it­ing the number of threads that are polling. This can help keep the re­ceiv­ing ap­plic­a­tion from being over­whelmed by too many re­quests; extra mes­sages queue up until the re­ceiver can pro­cess them.

                                                                                                                                                                                                          接收器应用程序通常对每个希望监视的通道(至少)使用一个线程,但它也可以使用单个线程来监视多个通道,这有助于在监视经常为空的通道时节省线程。要轮询单个通道,假设线程在消息到达之前没有任何事情可做,请使用在消息到达之前阻塞的receive版本。要使用单个线程轮询多个通道,或者在等待消息到达时执行其他工作,请使用带有超时或receiveNoWait()的 receive 版本,以便如果一个通道为空,线程将继续检查另一个通道或执行其他工作。

                                                                                                                                                                                                          A re­ceiver-ap­plic­a­tion typ­ic­ally uses one thread (at least) per chan­nel that it wishes to mon­itor, but it can also use a single thread to mon­itor mul­tiple chan­nels, which helps con­serve threads when mon­it­or­ing chan­nels that are fre­quently empty. To poll a single chan­nel, as­sum­ing that the thread has noth­ing to do until a mes­sage ar­rives, use a ver­sion of re­ceive that blocks until a mes­sage ar­rives. To poll mul­tiple chan­nels with a single thread, or to per­form other work while wait­ing for a mes­sage to arrive, use a ver­sion of re­ceive with a timeout or re­ceiveNoWait() so that if one chan­nel is empty, the thread goes on to check an­other chan­nel or per­form other work.

                                                                                                                                                                                                          轮询过多或阻塞线程时间过长的消费者可能效率低下,在这种情况下,事件驱动的消费者可能会更高效。多个轮询消费者可以是竞争消费者。消息调度程序可以实现为轮询消费者轮询消费者可以是选择性消费者;它也可以是持久订阅者。轮询消费者也可以事务客户端,以便消费者可以控制消息何时从通道中实际删除。

                                                                                                                                                                                                          A con­sumer that is polling too much or block­ing threads for too long can be in­ef­fi­cient, in which case an Event-Driven Con­sumer may be more ef­fi­cient. Mul­tiple Polling Con­sumers can be Com­pet­ing Con­sumers. A Mes­sage Dis­patcher can be im­ple­men­ted as a Polling Con­sumer. A Polling Con­sumer can be a Se­lect­ive Con­sumer; it can also be a Dur­able Sub­scriber. A Polling Con­sumer can also be a Trans­ac­tional Client so that the con­sumer can con­trol when the mes­sage is ac­tu­ally re­moved from the chan­nel.

                                                                                                                                                                                                          示例: JMS 接收

                                                                                                                                                                                                          Ex­ample: JMS Re­ceive

                                                                                                                                                                                                          在 JMS 中,消息使用者使用MessageConsumer.receive同步消费消息 [ JMS 1.1], [ Hapner ] 。

                                                                                                                                                                                                          In JMS, a mes­sage con­sumer uses Mes­sage­Con­sumer.re­ceive to con­sume a mes­sage syn­chron­ously [JMS 1.1], [Hapner].

                                                                                                                                                                                                          MessageConsumer 具有三种不同的接收方法:

                                                                                                                                                                                                          Mes­sage­Con­sumer has three dif­fer­ent re­ceive meth­ods:

                                                                                                                                                                                                          1. receive() 阻塞,直到消息可用,然后返回该消息。

                                                                                                                                                                                                          2. re­ceive() Blocks until a mes­sage is avail­able and then re­turns it.

                                                                                                                                                                                                          3. receiveNoWait() 检查一次消息并返回它或 null。

                                                                                                                                                                                                          4. re­ceiveNoWait() Checks once for a mes­sage and re­turns it or null.

                                                                                                                                                                                                          5. receive(long) 阻塞,直到消息可用并返回该消息,或者直到超时到期并返回 null。

                                                                                                                                                                                                          6. re­ceive(long) Blocks until a mes­sage is avail­able and re­turns it, or until the timeout ex­pires and re­turns null.

                                                                                                                                                                                                          例如,创建消费者并接收消息的代码非常简单:

                                                                                                                                                                                                          For ex­ample, the code to create a con­sumer and re­ceive a mes­sage is very simple:

                                                                                                                                                                                                          目的地 dest = // 获取目的地
                                                                                                                                                                                                          Session session = // 创建会话
                                                                                                                                                                                                          MessageConsumer 消费者 = session.createConsumer(dest);
                                                                                                                                                                                                          消息消息=consumer.receive();
                                                                                                                                                                                                          
                                                                                                                                                                                                          Des­tin­a­tion dest = // Get the des­tin­a­tion
                                                                                                                                                                                                          Ses­sion ses­sion = // Create the ses­sion
                                                                                                                                                                                                          Mes­sage­Con­sumer con­sumer = ses­sion.cre­ate­Con­sumer(dest);
                                                                                                                                                                                                          Mes­sage mes­sage = con­sumer.re­ceive();
                                                                                                                                                                                                          



                                                                                                                                                                                                          示例: .NET 接收

                                                                                                                                                                                                          Ex­ample: .NET Re­ceive

                                                                                                                                                                                                          在.NET中,消费者使用MessageQueue.Receive来同步消费消息[ SysMsg ] 。

                                                                                                                                                                                                          In .NET, a con­sumer uses Mes­sageQueue.Re­ceive to con­sume a mes­sage syn­chron­ously [SysMsg].

                                                                                                                                                                                                          MessageQueue客户端有多种接收方式。最简单的两个是

                                                                                                                                                                                                          A Mes­sageQueue client has sev­eral vari­ations of re­ceive. The two simplest are

                                                                                                                                                                                                          1. Receive() 阻塞直到消息可用,然后返回该消息。

                                                                                                                                                                                                          2. Re­ceive() Blocks until a mes­sage is avail­able, and then re­turns it.

                                                                                                                                                                                                          3. Receive(TimeSpan) 阻塞,直到消息可用并返回该消息,或者直到超时到期并引发MessageQueueException

                                                                                                                                                                                                          4. Re­ceive(TimeSpan) Blocks until a mes­sage is avail­able and re­turns it, or until the timeout ex­pires and throws Mes­sageQueueEx­cep­tion.

                                                                                                                                                                                                          从现有队列接收消息的代码非常简单:

                                                                                                                                                                                                          The code to re­ceive a mes­sage from an ex­ist­ing queue is quite simple:

                                                                                                                                                                                                          MessageQueue queue = // 获取队列
                                                                                                                                                                                                          消息消息=queue.Receive();
                                                                                                                                                                                                          
                                                                                                                                                                                                          Mes­sageQueue queue = // Get the queue
                                                                                                                                                                                                          Mes­sage mes­sage = queue.Re­ceive();
                                                                                                                                                                                                          



                                                                                                                                                                                                            事件驱动的消费者

                                                                                                                                                                                                            Event-Driven Consumer

                                                                                                                                                                                                            图形/eventdrivenconsumer_icon.gif

                                                                                                                                                                                                            应用程序需要在消息传递后立即使用它们。

                                                                                                                                                                                                            An ap­plic­a­tion needs to con­sume Mes­sages as soon as they're de­livered.

                                                                                                                                                                                                            应用程序如何在消息可用时自动使用消息?

                                                                                                                                                                                                            How can an ap­plic­a­tion auto­mat­ic­ally con­sume mes­sages as they become avail­able?



                                                                                                                                                                                                            轮询消费者的问题在于,当通道为空时,消费者会在轮询不存在的消息时阻塞线程和/或消耗进程时间。轮询使客户端能够控制消耗速度,但当没有东西可消耗时会浪费资源。

                                                                                                                                                                                                            The prob­lem with Polling Con­sumers is that when the chan­nel is empty, the con­sumer blocks threads and/or con­sumes pro­cess time while polling for mes­sages that are not there. Polling en­ables the client to con­trol the rate of con­sump­tion but wastes re­sources when there's noth­ing to con­sume.

                                                                                                                                                                                                            与其不断询问通道是否有消息要使用,不如通道能够告诉客户端何时有消息可用。因此,不要让消费者轮询消息,而是在消息可用时立即将消息提供给消费者。

                                                                                                                                                                                                            Rather than con­tinu­ously asking the chan­nel if it has mes­sages to con­sume, it would be better if the chan­nel could tell the client when a mes­sage is avail­able. For that matter, in­stead of making the con­sumer poll for the mes­sage, just give the mes­sage to the con­sumer as soon as the mes­sage be­comes avail­able.

                                                                                                                                                                                                            应用程序应该使用事件驱动的消费者,它是一种在消息在通道上传递时自动传递消息的消费者。

                                                                                                                                                                                                            The ap­plic­a­tion should use an Event-Driven Con­sumer, one that is auto­mat­ic­ally handed mes­sages as they're de­livered on the chan­nel.

                                                                                                                                                                                                            图形/10inf13.gif



                                                                                                                                                                                                            这也称为异步接收器,因为在回调线程传递消息之前接收器没有正在运行的线程。我们将其称为事件驱动的消费者,因为接收者的行为就像消息传递是一个触发接收者采取行动的事件。

                                                                                                                                                                                                            This is also known as an asyn­chron­ous re­ceiver, be­cause the re­ceiver does not have a run­ning thread until a call­back thread de­liv­ers a mes­sage. We call it an Event-Driven Con­sumer be­cause the re­ceiver acts like the mes­sage de­liv­ery is an event that trig­gers the re­ceiver into action.

                                                                                                                                                                                                            事件驱动消费者是当消息到达消费者的通道时由消息传递系统调用的对象。消费者通过应用程序 API 中的回调将消息传递给应用程序。(消息传递系统如何获取消息是特定于实现的,并且可能是也可能不是事件驱动的。消费者所知道的是,它可以在没有活动线程的情况下处于休眠状态,直到消息传递系统调用它并向其传递消息。)

                                                                                                                                                                                                            An Event-Driven Con­sumer is an object that is in­voked by the mes­saging system when a mes­sage ar­rives on the con­sumer's chan­nel. The con­sumer passes the mes­sage to the ap­plic­a­tion through a call­back in the ap­plic­a­tion's API. (How the mes­saging system gets the mes­sage is im­ple­ment­a­tion-spe­cific and may or may not be event-driven. All the con­sumer knows is that it can sit dormant with no active threads until it gets in­voked by the mes­saging system passing it a mes­sage.)

                                                                                                                                                                                                            事件驱动的消费者由消息传递系统调用,但它会调用特定于应用程序的回调。为了弥补这一差距,消费者拥有一个特定于应用程序的实现,该实现符合消息传递系统定义的已知 API。

                                                                                                                                                                                                            An Event-Driven Con­sumer is in­voked by the mes­saging system, yet it in­vokes an ap­plic­a­tion-spe­cific call­back. To bridge this gap, the con­sumer has an ap­plic­a­tion-spe­cific im­ple­ment­a­tion that con­forms to a known API defined by the mes­saging system.

                                                                                                                                                                                                            事件驱动消费者的代码由两部分组成:

                                                                                                                                                                                                            The code for an Event-Driven Con­sumer con­sists of two parts:

                                                                                                                                                                                                            1. 初始化 应用程序创建一个特定于应用程序的使用者并将其与特定的消息通道关联起来。这段代码运行一次后,消费者就准备好接收一系列消息。

                                                                                                                                                                                                            2. Ini­tial­iz­a­tion The ap­plic­a­tion cre­ates an ap­plic­a­tion-spe­cific con­sumer and as­so­ci­ates it with a par­tic­u­lar Mes­sage Chan­nel. After this code is run once, the con­sumer is ready to re­ceive a series of mes­sages.

                                                                                                                                                                                                            3. 消费者 接收消息并将其传递给应用程序进行处理。此代码针对每个正在使用的消息运行一次。

                                                                                                                                                                                                            4. Con­sump­tion The con­sumer re­ceives a mes­sage and passes it to the ap­plic­a­tion for pro­cess­ing. This code is run once per mes­sage being con­sumed.

                                                                                                                                                                                                            事件驱动的消费者序列

                                                                                                                                                                                                            Event-Driven Con­sumer Se­quence

                                                                                                                                                                                                            图形/10inf14.gif

                                                                                                                                                                                                            应用程序创建其自定义使用者并将其与通道关联。一旦消费者被初始化,它(和应用程序)就可以进入休眠状态,没有正在运行的线程,等待消息到达时被调用。

                                                                                                                                                                                                            The ap­plic­a­tion cre­ates its custom con­sumer and as­so­ci­ates it with the chan­nel. Once the con­sumer is ini­tial­ized, it (and the ap­plic­a­tion) can then go dormant, with no run­ning threads, wait­ing to be in­voked when a mes­sage ar­rives.

                                                                                                                                                                                                            当消息被传递时,消息传递系统调用消费者的消息接收事件方法并将消息作为参数传递。消费者使用应用程序的回调 API 将消息传递给应用程序。应用程序现在已收到消息并可以对其进行处理。一旦应用程序完成消息处理,它和消费者就可以再次休眠,直到下一条消息到达。通常,消息传递系统不会通过单个使用者运行多个线程,因此使用者一次只能处理一条消息。

                                                                                                                                                                                                            When a mes­sage is de­livered, the mes­saging system calls the con­sumer's mes­sage-re­ceived-event method and passes in the mes­sage as a para­meter. The con­sumer passes the mes­sage to the ap­plic­a­tion, using the ap­plic­a­tion's call­back API. The ap­plic­a­tion now has the mes­sage and can pro­cess it. Once the ap­plic­a­tion fin­ishes pro­cess­ing the mes­sage, it and the con­sumer can then go dormant again until the next mes­sage ar­rives. Typ­ic­ally, a mes­saging system will not run mul­tiple threads through a single con­sumer, so the con­sumer can pro­cess only one mes­sage at a time.

                                                                                                                                                                                                            事件驱动的 Consumer在消息可用时自动使用它们。为了更细粒度地控制消费率,请使用Polling Consumer 事件驱动的消费者可以是竞争的消费者。消息调度程序可以实现为事件驱动的消费者事件驱动的消费者可以是选择性消费者;它也可以是持久订阅者事务性客户端与事件驱动消费者的配合可能不如与轮询消费者的配合好;请参阅 JMS 示例。

                                                                                                                                                                                                            Event-Driven Con­sumers auto­mat­ic­ally con­sume mes­sages as they become avail­able. For more fine-grained con­trol of the con­sump­tion rate, use a Polling Con­sumer. Event-Driven Con­sumers can be Com­pet­ing Con­sumers. A Mes­sage Dis­patcher can be im­ple­men­ted as an Event-Driven Con­sumer. An Event-Driven Con­sumer can be a Se­lect­ive Con­sumer; it can also be a Dur­able Sub­scriber. Trans­ac­tional Cli­ents may not work as well with Event-Driven Con­sumers as they do with Polling Con­sumers; see the JMS ex­ample.

                                                                                                                                                                                                            示例: JMS 消息监听器

                                                                                                                                                                                                            Ex­ample: JMS Mes­sageL­istener

                                                                                                                                                                                                            在 JMS 中,事件驱动消费者是一个实现MessageListener接口 [Hapner ] 的类。该接口声明一个方法onMessage(Message)。消费者实现onMessage来处理消息。以下是 JMS 执行者的示例:

                                                                                                                                                                                                            In JMS, an Event-Driven Con­sumer is a class that im­ple­ments the Mes­sageL­istener in­ter­face [Hapner]. This in­ter­face de­clares a single method, on­Mes­sage(Mes­sage). The con­sumer im­ple­ments on­Mes­sage to pro­cess the mes­sage. Here is an ex­ample of a JMS per­former:

                                                                                                                                                                                                            公共类 MyEventDrivenConsumer 实现 MessageListener {
                                                                                                                                                                                                                公共无效onMessage(消息消息){
                                                                                                                                                                                                                    // 处理消息
                                                                                                                                                                                                                }
                                                                                                                                                                                                            }
                                                                                                                                                                                                            
                                                                                                                                                                                                            public class My­Event­Driv­en­Con­sumer im­ple­ments Mes­sageL­istener {
                                                                                                                                                                                                                public void on­Mes­sage(Mes­sage mes­sage) {
                                                                                                                                                                                                                    // Pro­cess the mes­sage
                                                                                                                                                                                                                }
                                                                                                                                                                                                            }
                                                                                                                                                                                                            

                                                                                                                                                                                                            事件驱动消费者的初始化部分创建所需的执行者对象(这是一个MessageListener实例)并将其与所需通道的消息消费者关联:

                                                                                                                                                                                                            The ini­tial­izer part of an Event-Driven Con­sumer cre­ates the de­sired per­former object (which is a Mes­sageL­istener in­stance) and as­so­ci­ates it with a mes­sage con­sumer for the de­sired chan­nel:

                                                                                                                                                                                                            目的地目的地 = // 获取目的地
                                                                                                                                                                                                            Session session = // 创建会话
                                                                                                                                                                                                            MessageConsumer 消费者 = session.createConsumer(destination);
                                                                                                                                                                                                            MessageListener 监听器 = new MyEventDrivenConsumer();
                                                                                                                                                                                                            消费者.setMessageListener(监听器);
                                                                                                                                                                                                            
                                                                                                                                                                                                            Des­tin­a­tion des­tin­a­tion = // Get the des­tin­a­tion
                                                                                                                                                                                                            Ses­sion ses­sion = // Create the ses­sion
                                                                                                                                                                                                            Mes­sage­Con­sumer con­sumer = ses­sion.cre­ate­Con­sumer(des­tin­a­tion);
                                                                                                                                                                                                            Mes­sageL­istener listener = new My­Event­Driv­en­Con­sumer();
                                                                                                                                                                                                            con­sumer.set­Mes­sageL­istener(listener);
                                                                                                                                                                                                            

                                                                                                                                                                                                            现在,当消息传递到目标时,JMS 提供程序将调用MyEventDrivenConsumer.onMessage,并将消息作为参数。

                                                                                                                                                                                                            Now, when a mes­sage is de­livered to the des­tin­a­tion, the JMS pro­vider will call My­Event­Driv­en­Con­sumer.on­Mes­sage with the mes­sage as a para­meter.

                                                                                                                                                                                                            请注意,在 JMS 中,同时也是事务客户端的事件驱动消费者将无法按预期工作。通常,当事务中的代码抛出异常时,事务会回滚,但MessageListener.onMessage签名不提供抛出异常(例如JMSException ),并且运行时异常被视为程序员错误。如果发生运行时异常,JMS 提供者将通过传递下一条消息进行响应,因此导致异常的消息会丢失 [ JMS 1.1 ]、[ Hapner ]。要成功实现事务性、事件驱动的行为,请使用消息驱动的 EJB [ EJB 2.0],[哈普纳]。

                                                                                                                                                                                                            Note that in JMS, an Event-Driven Con­sumer that is also a Trans­ac­tional Client will not work as ex­pec­ted. Nor­mally, a trans­ac­tion is rolled back when the code in the trans­ac­tion throws an ex­cep­tion, but the Mes­sageL­istener.on­Mes­sage sig­na­ture does not provide for an ex­cep­tion being thrown (such as JM­SEx­cep­tion), and a runtime ex­cep­tion is con­sidered pro­gram­mer error. If a runtime ex­cep­tion occurs, the JMS pro­vider re­sponds by de­liv­er­ing the next mes­sage, so the mes­sage that caused the ex­cep­tion is lost [JMS 1.1], [Hapner]. To suc­cess­fully achieve trans­ac­tional, event-driven be­ha­vior, use a mes­sage-driven EJB [EJB 2.0], [Hapner].



                                                                                                                                                                                                            示例: .NET ReceiveCompletedEventHandler

                                                                                                                                                                                                            Ex­ample: .NET Re­ceive­Com­plete­dE­ventHand­ler

                                                                                                                                                                                                            在 .NET 中,事件驱动消费者的执行者部分实现了一个ReceiveCompletedEventHandler 委托方法。此委托方法必须接受两个参数:一个是 MessageQueue 的对象,另一个来自ReceiveCompletedReceiveCompletedEventArgs 。 该方法使用参数从队列中获取消息并对其进行处理。以下是 .NET 执行者的示例:

                                                                                                                                                                                                            With .NET, the per­former part of an Event-Driven Con­sumer im­ple­ments a method that is a Re­ceive­Com­plete­dE­ventHand­ler del­eg­ate. This del­eg­ate method must accept two para­met­ers: an object that is the Mes­sageQueue and a Re­ceive­Com­plete­dEvent­Args that is the ar­gu­ments from the Re­ceive­Com­pleted event [SysMsg]. The method uses the ar­gu­ments to get the mes­sage from the queue and pro­cess it. Here is an ex­ample of a .NET per­former:

                                                                                                                                                                                                            公共静态无效MyEventDrivenConsumer(对象源,
                                                                                                                                                                                                                ReceiveCompletedEventArgs asyncResult)
                                                                                                                                                                                                            {
                                                                                                                                                                                                                消息队列 mq = (消息队列) 源;
                                                                                                                                                                                                                消息 m = mq.EndReceive(asyncResult.AsyncResult);
                                                                                                                                                                                                                // 处理消息
                                                                                                                                                                                                                mq.BeginReceive();
                                                                                                                                                                                                                返回;
                                                                                                                                                                                                            }
                                                                                                                                                                                                            
                                                                                                                                                                                                            public static void My­Event­Driv­en­Con­sumer(Object source,
                                                                                                                                                                                                                Re­ceive­Com­plete­dEvent­Args asyn­cRes­ult)
                                                                                                                                                                                                            {
                                                                                                                                                                                                                Mes­sageQueue mq = (Mes­sageQueue) source;
                                                                                                                                                                                                                Mes­sage m = mq.En­dRe­ceive(asyn­cRes­ult.Asyn­cRes­ult);
                                                                                                                                                                                                                // Pro­cess the mes­sage
                                                                                                                                                                                                                mq.Be­gin­Re­ceive();
                                                                                                                                                                                                                return;
                                                                                                                                                                                                            }
                                                                                                                                                                                                            

                                                                                                                                                                                                            事件驱动客户端的初始化程序部分指定队列应运行委托方法来处理ReceiveCompleted 事件

                                                                                                                                                                                                            The ini­tial­izer part of an event-driven client spe­cifies that the queue should run the del­eg­ate method to handle a Re­ceive­Com­pleted event:

                                                                                                                                                                                                            MessageQueue queue = // 获取队列
                                                                                                                                                                                                            队列.ReceiveCompleted +=
                                                                                                                                                                                                                新的 ReceiveCompletedEventHandler(MyEventDrivenConsumer);
                                                                                                                                                                                                            队列.BeginReceive();
                                                                                                                                                                                                            
                                                                                                                                                                                                            Mes­sageQueue queue = // Get the queue
                                                                                                                                                                                                            queue.Re­ceive­Com­pleted +=
                                                                                                                                                                                                                new Re­ceive­Com­plete­dE­ventHand­ler(My­Event­Driv­en­Con­sumer);
                                                                                                                                                                                                            queue.Be­gin­Re­ceive();
                                                                                                                                                                                                            

                                                                                                                                                                                                            现在,当消息传递到队列时,队列将发出ReceiveCompleted 事件,该事件将运行MyEventDrivenConsumer方法。

                                                                                                                                                                                                            Now, when a mes­sage is de­livered to the queue, the queue will issue a Re­ceive­Com­pleted event, which will run the My­Event­Driv­en­Con­sumer method.



                                                                                                                                                                                                              竞争的消费者

                                                                                                                                                                                                              Competing Consumers

                                                                                                                                                                                                              图形/竞争消费者_icon.gif

                                                                                                                                                                                                              应用程序正在使用消息传递。但是,它处理消息的速度无法与将消息添加到通道的速度一样快。

                                                                                                                                                                                                              An ap­plic­a­tion is using Mes­saging. How­ever, it cannot pro­cess mes­sages as fast as they're being added to the chan­nel.

                                                                                                                                                                                                              消息客户端如何同时处理多条消息?

                                                                                                                                                                                                              How can a mes­saging client pro­cess mul­tiple mes­sages con­cur­rently?



                                                                                                                                                                                                              消息按顺序通过消息通道到达,因此消费者的自然倾向是按顺序处理它们。然而,顺序消费可能太慢,并且消息可能在通道上堆积,这使得消息系统成为瓶颈并损害应用程序的整体吞吐量。发生这种情况的原因可能是通道上有多个发送方、网络中断导致消息积压,然后一次性全部传递、接收方中断导致积压、或者每条消息需要花费更多的精力来消费和执行。它确实可以创建和发送。

                                                                                                                                                                                                              Mes­sages arrive through a Mes­sage Chan­nel se­quen­tially, so the nat­ural in­clin­a­tion of a con­sumer is to pro­cess them se­quen­tially. How­ever, se­quen­tial con­sump­tion may be too slow, and mes­sages may pile up on the chan­nel, which makes the mes­saging system a bot­tle­neck and hurts over­all through­put of the ap­plic­a­tion. This can happen either be­cause of mul­tiple senders on the chan­nel, be­cause a net­work outage causes a back­log of mes­sages which are then de­livered all at once, be­cause a re­ceiver outage causes a back­log, or be­cause each mes­sage takes sig­ni­fic­antly more effort to con­sume and per­form than it does to create and send.

                                                                                                                                                                                                              应用程序可以使用多个通道,但一个通道可能成为瓶颈,而另一个通道则空着,并且发送者不知道要使用哪一个等效通道。然而,多个通道的优点是允许多个消费者(每个通道一个)同时处理消息。即使这有效,应用程序定义的通道数量仍然会限制吞吐量。

                                                                                                                                                                                                              The ap­plic­a­tion could use mul­tiple chan­nels, but one chan­nel might become a bot­tle­neck while an­other sits empty, and a sender would not know which one of equi­val­ent chan­nels to use. Mul­tiple chan­nels would have the ad­vant­age, how­ever, of en­abling mul­tiple con­sumers (one per chan­nel), pro­cess­ing mes­sages con­cur­rently. Even if this worked, though, the number of chan­nels the ap­plic­a­tion defined would still limit the through­put.

                                                                                                                                                                                                              我们需要一种让渠道拥有多个消费者的方法。

                                                                                                                                                                                                              What is needed is a way for a chan­nel to have mul­tiple con­sumers.

                                                                                                                                                                                                              在单个通道上创建多个竞争消费者,以便消费者可以同时处理多个消息。

                                                                                                                                                                                                              Create mul­tiple Com­pet­ing Con­sumers on a single chan­nel so that the con­sumers can pro­cess mul­tiple mes­sages con­cur­rently.

                                                                                                                                                                                                              图形/10inf15.gif



                                                                                                                                                                                                              竞争消费者是多个消费者,它们都是为了从单个点对点通道接收消息而创建的。当通道传递消息时,任何消费者都可能收到它。消息传递系统的实现决定了哪个消费者实际接收消息,但实际上消费者相互竞争成为接收者。一旦消费者收到消息,它就可以委托给其应用程序的其余部分来帮助处理该消息。(此解决方案仅适用于点对点通道;发布-订阅通道上的多个消费者只需为每条消息创建更多副本。)

                                                                                                                                                                                                              Com­pet­ing Con­sumers are mul­tiple con­sumers that are all cre­ated to re­ceive mes­sages from a single Point-to-Point Chan­nel. When the chan­nel de­liv­ers a mes­sage, any of the con­sumers could po­ten­tially re­ceive it. The mes­saging system's im­ple­ment­a­tion de­term­ines which con­sumer ac­tu­ally re­ceives the mes­sage, but in effect the con­sumers com­pete with each other to be the re­ceiver. Once a con­sumer re­ceives a mes­sage, it can del­eg­ate to the rest of its ap­plic­a­tion to help pro­cess the mes­sage. (This solu­tion only works with Point-to-Point Chan­nels; mul­tiple con­sumers on a Pub­lish-Sub­scribe Chan­nel just create more copies of each mes­sage.)

                                                                                                                                                                                                              竞争消费者序列

                                                                                                                                                                                                              Com­pet­ing Con­sumers Se­quence

                                                                                                                                                                                                              图形/10inf16.gif

                                                                                                                                                                                                              每个竞争消费者都在自己的线程中运行,以便它们都可以同时消费消息。当通道传递消息时,消息传递系统的事务控制确保只有一个消费者成功接收该消息。当该消费者正在处理消息时,通道可以传递其他消息,其他消费者可以同时使用和处理这些消息。通道协调消费者,确保每个消费者收到不同的消息;消费者不必相互协调。

                                                                                                                                                                                                              Each of the Com­pet­ing Con­sumers runs in its own thread so that they all can con­sume mes­sages con­cur­rently. When the chan­nel de­liv­ers a mes­sage, the mes­saging system's trans­ac­tional con­trols ensure that only one of the con­sumers suc­cess­fully re­ceives the mes­sage. While that con­sumer is pro­cess­ing the mes­sage, the chan­nel can de­liver other mes­sages, which other con­sumers can con­cur­rently con­sume and pro­cess. The chan­nel co­ordin­ates the con­sumers, making sure that each re­ceives a dif­fer­ent mes­sage; the con­sumers do not have to co­ordin­ate with each other.

                                                                                                                                                                                                              每个消费者同时处理不同的消息,因此瓶颈变成了通道向消费者提供消息的速度,而不是消费者处理消息需要多长时间。有限数量的消费者可能仍然是瓶颈,但只要有可用的计算资源,增加消费者数量就可以缓解该限制。

                                                                                                                                                                                                              Each con­sumer pro­cesses a dif­fer­ent mes­sage con­cur­rently, so the bot­tle­neck be­comes how quickly the chan­nel can feed mes­sages to the con­sumers in­stead of how long it takes a con­sumer to pro­cess a mes­sage. A lim­ited number of con­sumers may still be a bot­tle­neck, but in­creas­ing the number of con­sumers can al­le­vi­ate that con­straint as long as there are avail­able com­put­ing re­sources.

                                                                                                                                                                                                              要并发运行,每个使用者必须在自己的线程中运行。对于轮询消费者来说,这意味着每个消费者必须有自己的线程来同时执行轮询。对于事件驱动的消费者,消息系统必须为每个并发消费者使用一个线程;该线程将用于将消息传递给消费者,并由消费者用来处理消息。

                                                                                                                                                                                                              To run con­cur­rently, each con­sumer must run in its own thread. For Polling Con­sumers, this means that each con­sumer must have its own thread to per­form the polling con­cur­rently. For Event-Driven Con­sumers, the mes­saging system must use a thread per con­cur­rent con­sumer; that thread will be used to hand the mes­sage to the con­sumer and will be used by the con­sumer to pro­cess the mes­sage.

                                                                                                                                                                                                              复杂的消息传递系统将检测通道上的竞争消费者,并在内部提供消息调度程序,以确保每条消息仅传递给单个消费者。这有助于避免多个消费者都认为自己是单个消息的消费者时出现的冲突。不太复杂的消息系统将允许多个消费者尝试使用同一条消息。当这种情况发生时,无论哪个消费者首先提交其交易,都会获胜;那么其他消费者将无法成功提交,并且必须回滚他们的事务。

                                                                                                                                                                                                              A soph­ist­ic­ated mes­saging system will detect com­pet­ing con­sumers on a chan­nel and in­tern­ally provide a Mes­sage Dis­patcher that en­sures that each mes­sage is only de­livered to a single con­sumer. This helps avoid con­flicts that would arise if mul­tiple con­sumers each thought they were the con­sumer of a single mes­sage. A less soph­ist­ic­ated mes­saging system will allow mul­tiple con­sumers to at­tempt to con­sume the same mes­sage. When this hap­pens, whichever con­sumer com­mits its trans­ac­tion first wins; then the other con­sumers will not be able to commit suc­cess­fully and will have to roll back their trans­ac­tions.

                                                                                                                                                                                                              允许多个消费者尝试使用同一消息的消息传递系统可能会使事务客户端效率非常低。客户端认为它有一条消息,使用它,花费精力处理该消息,然后尝试提交但无法提交(因为该消息已被竞争对手使用)。频繁执行工作只是为了回滚会损害吞吐量,而此解决方案的目的是提高吞吐量。因此,应仔细衡量竞争性交易消费者的表现;它可能因不同的消息传递系统实现和配置而有很大差异。

                                                                                                                                                                                                              A mes­saging system that allows mul­tiple con­sumers to at­tempt con­sum­ing the same mes­sage can make a Trans­ac­tional Client very in­ef­fi­cient. The client thinks it has a mes­sage, con­sumes it, spends effort pro­cess­ing the mes­sage, then tries to commit and cannot (be­cause the mes­sage has already been con­sumed by a com­pet­itor). Fre­quently per­form­ing work just to roll it back hurts through­put, whereas the point of this solu­tion is to in­crease through­put. Thus, the per­form­ance of com­pet­ing trans­ac­tional con­sumers should be meas­ured care­fully; it could vary sig­ni­fic­antly on dif­fer­ent mes­saging system im­ple­ment­a­tions and con­fig­ur­a­tions.

                                                                                                                                                                                                              竞争消费者不仅可以用于在单个应用程序中的多个消费者线程之间分散负载;它们还可以将消耗负载分散到多个应用程序(例如进程)。这样,如果一个应用程序无法足够快地消费消息,则多个消费者应用程序(可能每个应用程序都使用多个消费者线程)可以解决该问题。使用多个线程在多台计算机上运行多个应用程序来消费消息的能力提供了几乎无限的消息处理能力,其中唯一的限制是消息传递系统将消息从通道传递给消费者的能力。

                                                                                                                                                                                                              Not only can Com­pet­ing Con­sumers be used to spread load across mul­tiple con­sumer threads in a single ap­plic­a­tion; they can also spread the con­sump­tion load across mul­tiple ap­plic­a­tions (e.g., pro­cesses). This way, if one ap­plic­a­tion cannot con­sume mes­sages fast enough, mul­tiple con­sumer ap­plic­a­tion­sper­haps with each em­ploy­ing mul­tiple con­sumer thread­scan attack the prob­lem. The abil­ity to have mul­tiple ap­plic­a­tions run­ning on mul­tiple com­puters using mul­tiple threads to con­sume mes­sages provides vir­tu­ally un­lim­ited mes­sage pro­cess­ing ca­pa­city, where the only limit is the mes­saging system's abil­ity to de­liver mes­sages from the chan­nel to the con­sumers.

                                                                                                                                                                                                              竞争消费者的协调取决于每个消息系统的实现。如果客户端想要自己实现这种协调,则应该使用消息调度程序竞争消费者可以是轮询消费者、事件驱动消费者或其组合。竞争的事务客户端可能会浪费大量精力来处理其接收操作未成功提交且必须回滚的消息。

                                                                                                                                                                                                              The co­ordin­a­tion of com­pet­ing con­sumers de­pends on each mes­saging system's im­ple­ment­a­tion. If the client wants to im­ple­ment this co­ordin­a­tion itself, it should use a Mes­sage Dis­patcher. Com­pet­ing Con­sumers can be Polling Con­sumers, Event-Driven Con­sumers, or a com­bin­a­tion thereof. Com­pet­ing Trans­ac­tional Cli­ents can waste sig­ni­fic­ant effort pro­cess­ing mes­sages whose re­ceive op­er­a­tions do not commit suc­cess­fully and have to be rolled back.

                                                                                                                                                                                                              示例: 简单的 JMS 竞争消费者

                                                                                                                                                                                                              Ex­ample: Simple JMS Com­pet­ing Con­sumers

                                                                                                                                                                                                              这是一个如何用 Java 实现竞争消费者的简单示例。外部驱动程序/管理器对象(未显示)运行其中的几个。它在自己的线程中运行每个线程,并调用stopRunning()使其停止。

                                                                                                                                                                                                              This is a simple ex­ample of how to im­ple­ment a com­pet­ing con­sumer in Java. An ex­ternal driver/man­ager object (not shown) runs a couple of them. It runs each one in its own thread and calls sto­pRun­ning() to make it stop.

                                                                                                                                                                                                              JMS 会话必须是单线程的 [ JMS 1.1 ]、[ Hapner ]。单个会话序列化消息消费的顺序 [JMS 11],[ Hapner ]。因此,为了使每个竞争消费者在自己的线程中正常工作,并且为了使消费者能够并行消费消息,每个消费者必须有自己的Session (因此也有自己的MessageConsumer ) 。JMS 规范没有指定并发QueueReceiver(例如,竞争消费者)应该有效,甚至要求这种方法完全有效。因此,使用此技术的应用程序不被认为是可移植的,并且可能与不同的 JMS 提供者 [ JMS 1.1 ]、[ Hapner ] 一起以不同的方式工作。

                                                                                                                                                                                                              A JMS ses­sion must be single-threaded [JMS 1.1], [Hapner]. A single ses­sion seri­al­izes the order of mes­sage con­sump­tion [JMS 11], [Hapner]. So, for each com­pet­ing con­sumer to work prop­erly in its own thread, and for the con­sumers to be able to con­sume mes­sages in par­al­lel, each con­sumer must have its own Ses­sion (and there­fore its own Mes­sage­Con­sumer). The JMS spe­cific­a­tion does not spe­cify the se­mantics of how con­cur­rent QueueRe­ceiv­ers (e.g., Com­pet­ing Con­sumers) should work, or even re­quire that this ap­proach work at all. Thus, ap­plic­a­tions that use this tech­nique are not as­sumed to be port­able and may work dif­fer­ently with dif­fer­ent JMS pro­viders [JMS 1.1], [Hapner].

                                                                                                                                                                                                              消费者类实现Runnable,以便可以在自己的线程中运行;这允许消费者同时运行。所有消费者共享相同的Connection ,但每个消费者都创建自己的Session ,这很重要,因为每个会话只能支持单个线程。每个消费者重复从队列接收消息并处理它。

                                                                                                                                                                                                              The con­sumer class im­ple­ments Run­nable so that it can run in its own thread; this allows the con­sumers to run con­cur­rently. All of the con­sumers share the same Con­nec­tion, but each cre­ates its own Ses­sion, which is im­port­ant, since each ses­sion can only sup­port a single thread. Each con­sumer re­peatedly re­ceives a mes­sage from the queue and pro­cesses it.

                                                                                                                                                                                                              
                                                                                                                                                                                                              导入 javax.jms.Connection;
                                                                                                                                                                                                              导入 javax.jms.Destination;
                                                                                                                                                                                                              导入 javax.jms.JMSException;
                                                                                                                                                                                                              导入javax.jms.Message;
                                                                                                                                                                                                              导入 javax.jms.MessageConsumer;
                                                                                                                                                                                                              导入javax.jms.Session;
                                                                                                                                                                                                              导入 javax.naming.NamingException;
                                                                                                                                                                                                              
                                                                                                                                                                                                              公共类 CompetingConsumer 实现 Runnable {
                                                                                                                                                                                                              
                                                                                                                                                                                                                  私有 int 执行者ID;
                                                                                                                                                                                                                  私有MessageConsumer消费者;
                                                                                                                                                                                                                  私有布尔值正在运行;
                                                                                                                                                                                                              
                                                                                                                                                                                                                  受保护的竞争消费者() {
                                                                                                                                                                                                                      极好的();
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              
                                                                                                                                                                                                                  公共静态CompetingConsumer newConsumer(int id,
                                                                                                                                                                                                              图形/ccc.gif连接连接,
                                                                                                                                                                                                                                                              字符串队列名称)
                                                                                                                                                                                                                      抛出 JMSException、NamingException {
                                                                                                                                                                                                                      CompetingConsumer 消费者 = new CompetingConsumer();
                                                                                                                                                                                                                      Consumer.initialize(id, 连接, 队列名称);
                                                                                                                                                                                                                      退货消费者;
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              
                                                                                                                                                                                                                  protected void 初始化(int id,Connection 连接,
                                                                                                                                                                                                              图形/ccc.gif字符串队列名称)
                                                                                                                                                                                                                      抛出 JMSException、NamingException {
                                                                                                                                                                                                                      表演者ID = id;
                                                                                                                                                                                                                      会话会话 = connection.createSession(false, 会话
                                                                                                                                                                                                              图形/ccc.gif.AUTO_ACKNOWLEDGE);
                                                                                                                                                                                                                      目标调度程序队列 = JndiUtil.getDestination
                                                                                                                                                                                                              图形/ccc.gif(队列名称);
                                                                                                                                                                                                                      消费者 = session.createConsumer(dispatcherQueue);
                                                                                                                                                                                                                      正在运行 = true;
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              
                                                                                                                                                                                                                  公共无效运行(){
                                                                                                                                                                                                                      尝试 {
                                                                                                                                                                                                                          while (正在运行())
                                                                                                                                                                                                                              接收同步();
                                                                                                                                                                                                                      } catch (异常 e) {
                                                                                                                                                                                                                          e.printStackTrace();
                                                                                                                                                                                                                      }
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              
                                                                                                                                                                                                                  私有同步布尔 isRunning() {
                                                                                                                                                                                                                      返回正在运行;
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              
                                                                                                                                                                                                                  公共同步无效stopRunning(){
                                                                                                                                                                                                                      正在运行=假;
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              
                                                                                                                                                                                                                  private void receiveSync() 抛出 JMSException,
                                                                                                                                                                                                              图形/ccc.gif中断异常{
                                                                                                                                                                                                                      消息消息=consumer.receive();
                                                                                                                                                                                                                      如果(消息!= null)
                                                                                                                                                                                                                          处理消息(消息);
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              
                                                                                                                                                                                                                  私有无效processMessage(消息消息)
                                                                                                                                                                                                                    抛出 JMSException、InterruptedException {
                                                                                                                                                                                                                      int id = message.getIntProperty("cust_id");
                                                                                                                                                                                                              
                                                                                                                                                                                                                      System.out.println(System.currentTimeMillis() + ":
                                                                                                                                                                                                              图形/ccc.gif表演者#”
                                                                                                                                                                                                                        + PerformerID + " 开始;消息 ID " + id);
                                                                                                                                                                                                                      线程.sleep(500);
                                                                                                                                                                                                                      System.out.println(System.currentTimeMillis() + ":
                                                                                                                                                                                                              图形/ccc.gif表演者#”
                                                                                                                                                                                                                        + 表演者ID + “正在处理。”);
                                                                                                                                                                                                                      线程.sleep(500);
                                                                                                                                                                                                                      System.out.println(System.currentTimeMillis() + ":
                                                                                                                                                                                                              图形/ccc.gif表演者#”
                                                                                                                                                                                                                        + 表演者ID + “完成。”);
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              }
                                                                                                                                                                                                              
                                                                                                                                                                                                              
                                                                                                                                                                                                              import javax.jms.Con­nec­tion;
                                                                                                                                                                                                              import javax.jms.Des­tin­a­tion;
                                                                                                                                                                                                              import javax.jms.JM­SEx­cep­tion;
                                                                                                                                                                                                              import javax.jms.Mes­sage;
                                                                                                                                                                                                              import javax.jms.Mes­sage­Con­sumer;
                                                                                                                                                                                                              import javax.jms.Ses­sion;
                                                                                                                                                                                                              import javax.naming.NamingEx­cep­tion;
                                                                                                                                                                                                              
                                                                                                                                                                                                              public class Com­pet­ing­Con­sumer im­ple­ments Run­nable {
                                                                                                                                                                                                              
                                                                                                                                                                                                                  private int per­formerID;
                                                                                                                                                                                                                  private Mes­sage­Con­sumer con­sumer;
                                                                                                                                                                                                                  private boolean is­Run­ning;
                                                                                                                                                                                                              
                                                                                                                                                                                                                  pro­tec­ted Com­pet­ing­Con­sumer() {
                                                                                                                                                                                                                      super();
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              
                                                                                                                                                                                                                  public static Com­pet­ing­Con­sumer new­Con­sumer(int id,
                                                                                                                                                                                                               Con­nec­tion con­nec­tion,
                                                                                                                                                                                                                                                              String queueName)
                                                                                                                                                                                                                      throws JM­SEx­cep­tion, NamingEx­cep­tion {
                                                                                                                                                                                                                      Com­pet­ing­Con­sumer con­sumer = new Com­pet­ing­Con­sumer();
                                                                                                                                                                                                                      con­sumer.ini­tial­ize(id, con­nec­tion, queueName);
                                                                                                                                                                                                                      return con­sumer;
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              
                                                                                                                                                                                                                  pro­tec­ted void ini­tial­ize(int id, Con­nec­tion con­nec­tion,
                                                                                                                                                                                                               String queueName)
                                                                                                                                                                                                                      throws JM­SEx­cep­tion, NamingEx­cep­tion {
                                                                                                                                                                                                                      per­formerID = id;
                                                                                                                                                                                                                      Ses­sion ses­sion = con­nec­tion.cre­ateSes­sion(false, Ses­sion
                                                                                                                                                                                                              .AUTO_AC­KNOW­LEDGE);
                                                                                                                                                                                                                      Des­tin­a­tion dis­patch­er­Queue = Jn­di­Util.get­Des­tin­a­tion
                                                                                                                                                                                                              (queueName);
                                                                                                                                                                                                                      con­sumer = ses­sion.cre­ate­Con­sumer(dis­patch­er­Queue);
                                                                                                                                                                                                                      is­Run­ning = true;
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              
                                                                                                                                                                                                                  public void run() {
                                                                                                                                                                                                                      try {
                                                                                                                                                                                                                          while (is­Run­ning())
                                                                                                                                                                                                                              re­ceive­Sync();
                                                                                                                                                                                                                      } catch (Ex­cep­tion e) {
                                                                                                                                                                                                                          e.print­Stack­Trace();
                                                                                                                                                                                                                      }
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              
                                                                                                                                                                                                                  private syn­chron­ized boolean is­Run­ning() {
                                                                                                                                                                                                                      return is­Run­ning;
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              
                                                                                                                                                                                                                  public syn­chron­ized void sto­pRun­ning() {
                                                                                                                                                                                                                      is­Run­ning = false;
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              
                                                                                                                                                                                                                  private void re­ceive­Sync() throws JM­SEx­cep­tion,
                                                                                                                                                                                                               In­ter­rup­te­dEx­cep­tion {
                                                                                                                                                                                                                      Mes­sage mes­sage = con­sumer.re­ceive();
                                                                                                                                                                                                                      if (mes­sage != null)
                                                                                                                                                                                                                          pro­cess­Mes­sage(mes­sage);
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              
                                                                                                                                                                                                                  private void pro­cess­Mes­sage(Mes­sage mes­sage)
                                                                                                                                                                                                                    throws JM­SEx­cep­tion, In­ter­rup­te­dEx­cep­tion {
                                                                                                                                                                                                                      int id = mes­sage.getInt­Prop­erty("cust_id");
                                                                                                                                                                                                              
                                                                                                                                                                                                                      System.out.println(System.cur­rent­Time­Mil­lis() + ":
                                                                                                                                                                                                               Per­former #"
                                                                                                                                                                                                                        + per­formerID + " start­ing; mes­sage ID " + id);
                                                                                                                                                                                                                      Thread.sleep(500);
                                                                                                                                                                                                                      System.out.println(System.cur­rent­Time­Mil­lis() + ":
                                                                                                                                                                                                               Per­former #"
                                                                                                                                                                                                                        + per­formerID + " pro­cess­ing.");
                                                                                                                                                                                                                      Thread.sleep(500);
                                                                                                                                                                                                                      System.out.println(System.cur­rent­Time­Mil­lis() + ":
                                                                                                                                                                                                               Per­former #"
                                                                                                                                                                                                                        + per­formerID + " fin­ished.");
                                                                                                                                                                                                                  }
                                                                                                                                                                                                              }
                                                                                                                                                                                                              

                                                                                                                                                                                                              因此,实现一个简单的竞争消费者很容易。主要技巧是让消费者成为Runnable并在自己的线程中运行它。

                                                                                                                                                                                                              So, im­ple­ment­ing a simple Com­pet­ing Con­sumer is easy. The main trick is to make the con­sumer a Run­nable and run it in its own thread.



                                                                                                                                                                                                                消息调度程序

                                                                                                                                                                                                                Message Dispatcher

                                                                                                                                                                                                                图形/messagedispatcher_icon.gif

                                                                                                                                                                                                                应用程序正在使用消息传递。应用程序需要单个消息通道上的多个使用者以协调的方式工作。

                                                                                                                                                                                                                An ap­plic­a­tion is using Mes­saging. The ap­plic­a­tion needs mul­tiple con­sumers on a single Mes­sage Chan­nel to work in a co­ordin­ated fash­ion.

                                                                                                                                                                                                                单个通道上的多个消费者如何协调他们的消息处理?

                                                                                                                                                                                                                How can mul­tiple con­sumers on a single chan­nel co­ordin­ate their mes­sage pro­cess­ing?



                                                                                                                                                                                                                单个点对点通道上的多个消费者充当竞争消费者。当消费者可以互换时,这很好,但它不允许专门化消费者,以便某些消费者能够更好地消费某些消息。

                                                                                                                                                                                                                Mul­tiple con­sumers on a single Point-to-Point Chan­nel act as Com­pet­ing Con­sumers. That's fine when the con­sumers are in­ter­change­able, but it does not allow for spe­cial­iz­ing the con­sumers so that cer­tain con­sumers are better able to con­sume cer­tain mes­sages.

                                                                                                                                                                                                                单个发布-订阅通道上的多个消费者将无法按预期工作。这些消费者不会分配消息负载,而是重复工作。

                                                                                                                                                                                                                Mul­tiple con­sumers on a single Pub­lish-Sub­scribe Chan­nel won't work as in­ten­ded. Rather than dis­trib­ute the mes­sage load, these con­sumers will du­plic­ate the effort.

                                                                                                                                                                                                                选择性消费者可以但是,并非所有消息系统都支持此功能。即使在那些这样做的人中,他们也可能不支持基于消息正文中的值的选择。它们的选择器值表达式可能太简单而无法充分区分消息,或者重复评估这些表达式的性能可能很慢。可能有许多表达式需要以协调的方式仔细设计,以便它们不会重叠,但也不会留下任何未处理的选择器值。他们可能需要为其他使用者未处理或意外的选择器值实现默认情况。

                                                                                                                                                                                                                Se­lect­ive Con­sumers can be used as spe­cial­ized con­sumers. How­ever, not all mes­saging sys­tems sup­port this fea­ture. Even among those that do, they may not sup­port se­lec­tion based on values in the body of the mes­sage. Their se­lector value ex­pres­sions may be too simple to ad­equately dis­tin­guish among the mes­sages, or the per­form­ance of re­peatedly eval­u­at­ing those ex­pres­sions may be slow. There may be nu­mer­ous ex­pres­sions that need to be care­fully de­signed in a co­ordin­ated fash­ion so that they do not over­lap but also do not leave any se­lector values un­handled. They may need to im­ple­ment a de­fault case for se­lector values that are not handled by other con­sumers or that are un­ex­pec­ted.

                                                                                                                                                                                                                数据类型通道可但类型系统可能太大且多样化,无法证明为每种类型创建单独的通道是合理的。或者,类型可能基于动态变化的标准,这很难用静态通道集来处理。企业可能已经需要大量的通道,这会给消息传递系统带来负担,并且为不同的消息类型增加许多这些通道可能只是需要太多的通道。

                                                                                                                                                                                                                Data­type Chan­nels can be used to keep dif­fer­ent types of mes­sages sep­ar­ate and to enable con­sumers to spe­cial­ize for those mes­sage types. But the type system may be too large and varied to jus­tify cre­at­ing a sep­ar­ate chan­nel for each type. Or, the types may be based on dy­nam­ic­ally chan­ging cri­teria, which are dif­fi­cult to handle with a static set of chan­nels. The en­ter­prise may already re­quire a huge number of chan­nels, taxing the mes­saging system, and mul­tiply­ing many of those chan­nels for dis­tinct mes­sage types may simply re­quire too many chan­nels.

                                                                                                                                                                                                                如果消费者共同努力,这些问题都可以得到解决。他们可以通过了解其他消费者是否已经处理了该工作来避免重复工作。他们可以是专业化的;如果一个消费者收到了与其专业相关的错误消息,它可以将该消息传递给另一个具有正确专业的消费者。如果应用程序有太多通道进入,它可以通过让所有消费者共享一个通道来节省通道;他们将进行协调,以确保将正确的信息传达给正确的消费者。

                                                                                                                                                                                                                Each of these prob­lems could be solved if con­sumers could work to­gether. They could avoid du­plic­at­ing work by being aware if an­other con­sumer had already pro­cessed that work. They could be spe­cial­ized; if a con­sumer got the wrong kind of mes­sage for its spe­cialty, it could hand off the mes­sage to an­other con­sumer with the right spe­cialty. If an ap­plic­a­tion had too many chan­nels coming in, it could save chan­nels by having all of its con­sumers share a single chan­nel; they would co­ordin­ate to make sure that the right mes­sages went to the right con­sumers.

                                                                                                                                                                                                                唉,消费者是非常独立的对象,很难协调。让专门的消费者足够通用来处理任何消息并将其传递给每个消费者会增加大量的设计和处理开销。他们都必须互相了解,以便可以分派工作,并且他们都需要知道其他人中哪些人在忙,以免消费者在处理另一条消息时向其提供要处理的消息。让消费者一起工作将从根本上改变典型的消费者设计。

                                                                                                                                                                                                                Alas, con­sumers are very in­de­pend­ent ob­jects that are dif­fi­cult to co­ordin­ate. Making spe­cial­ized con­sumers gen­eral enough to handle any mes­sage and hand it off would add a lot of design and pro­cess­ing over­head to each con­sumer. They would all have to know about each other so they could hand off work, and they would all need to know which of the others were busy so as not to give a con­sumer a mes­sage to pro­cess while it's already pro­cess­ing an­other. Making con­sumers work to­gether would rad­ic­ally change the typ­ical con­sumer design.

                                                                                                                                                                                                                中介模式[GoF ]提供了一些帮助。中介者协调一组对象,以便它们不需要知道如何相互协调。我们需要一个中介者来协调通道的消费者。然后,每个消费者可以专注于处理特定类型的消息,而协调器可以确保正确的消息到达正确的消费者。

                                                                                                                                                                                                                The Me­di­ator pat­tern [GoF] offers some help. A Me­di­ator co­ordin­ates a group of ob­jects so that they don't need to know how to co­ordin­ate with each other. What we need for mes­saging is a me­di­ator that co­ordin­ates the con­sumers for a chan­nel. Then, each con­sumer could focus on pro­cess­ing a par­tic­u­lar kind of mes­sage, and the co­ordin­ator could make sure the right mes­sage gets to the right con­sumer.

                                                                                                                                                                                                                在通道上创建一个消息调度程序,它将使用来自通道的消息并将它们分发给执行者。

                                                                                                                                                                                                                Create a Mes­sage Dis­patcher on a chan­nel that will con­sume mes­sages from a chan­nel and dis­trib­ute them to per­formers.

                                                                                                                                                                                                                图形/10inf17.gif



                                                                                                                                                                                                                消息调度程序由两部分组成

                                                                                                                                                                                                                A Mes­sage Dis­patcher con­sists of two parts:

                                                                                                                                                                                                                1. 调度 程序使用来自通道的消息并将每条消息分发给执行者的对象。

                                                                                                                                                                                                                2. Dis­patcher The object that con­sumes mes­sages from a chan­nel and dis­trib­utes each mes­sage to a per­former.

                                                                                                                                                                                                                3. Performer调度程序提供消息并处理该消息的对象。

                                                                                                                                                                                                                4. Per­former The object that is given the mes­sage by the dis­patcher and pro­cesses it.

                                                                                                                                                                                                                消息调度程序接收到消息时,它会获取执行者并将消息调度给执行者进行处理。执行者可以委托给其应用程序的其余部分来帮助处理其消息。执行者可以由调度员新创建或者可以从可用执行者池中选择。每个执行者可以在自己的线程中运行以同时处理消息。所有执行者可能适合所有消息,或者调度程序可以根据消息的属性将消息与专门的执行者匹配。

                                                                                                                                                                                                                When a Mes­sage Dis­patcher re­ceives a mes­sage, it ob­tains a per­former and dis­patches the mes­sage to the per­former to pro­cess it. A per­former can del­eg­ate to the rest of its ap­plic­a­tion to help pro­cess its mes­sage. The per­former could be newly cre­ated by the dis­patcher or could be se­lec­ted from a pool of avail­able per­formers. Each per­former can run in its own thread to pro­cess mes­sages con­cur­rently. All per­formers may be ap­pro­pri­ate for all mes­sages, or the dis­patcher may match a mes­sage to a spe­cial­ized per­former based on prop­er­ties of the mes­sage.

                                                                                                                                                                                                                消息调度程序序列

                                                                                                                                                                                                                Mes­sage Dis­patcher Se­quence

                                                                                                                                                                                                                图形/10inf18.gif

                                                                                                                                                                                                                当调度程序收到消息时,它会将消息委托给可用的执行者来处理它。如果执行者使用调度程序的线程处理消息,则调度程序将阻塞,直到执行者完成对消息的处理。相反,如果执行者在自己的线程中处理消息,那么一旦调度程序启动该线程,它就可以立即开始接收其他消息并将它们委托给其他执行者,以便并发处理消息。这样,消息的消耗速度与调度程序接收和委托消息的速度一样快,而不管每条消息的处理时间有多长。

                                                                                                                                                                                                                When the dis­patcher re­ceives a mes­sage, it del­eg­ates the mes­sage to an avail­able per­former to pro­cess it. If the per­former pro­cesses the mes­sage using the dis­patcher's thread, then the dis­patcher blocks until the per­former is fin­ished pro­cess­ing the mes­sage. Con­versely, if the per­former pro­cesses the mes­sage in its own thread, then once the dis­patcher starts that thread, it can im­me­di­ately start re­ceiv­ing other mes­sages and del­eg­at­ing them to other per­formers so that the mes­sages are pro­cessed con­cur­rently. This way, mes­sages can be con­sumed as fast as the dis­patcher can re­ceive and del­eg­ate them, re­gard­less of how long each mes­sage takes to pro­cess.

                                                                                                                                                                                                                调度程序充当单个通道和一组执行者之间的一对多连接。表演者完成了大部分工作;调度程序只是充当匹配者,将每条消息与可用的执行者进行匹配,并且只要执行者在自己的线程中运行,就不会阻塞。调度程序接收消息,然后将其发送给执行者进行处理。由于调度程序所做的工作相对较少并且不会阻塞,因此它可以像消息传递系统提供消息一样快地调度消息,从而避免成为瓶颈。

                                                                                                                                                                                                                A dis­patcher acts as a one-to-many con­nec­tion between a single chan­nel and a group of per­formers. The per­formers do most of the work; the dis­patcher just acts as a match­maker, match­ing each mes­sage with an avail­able per­former, and does not block as long as the per­formers run in their own threads. The dis­patcher re­ceives the mes­sage and then sends it to a per­former to pro­cess it. Be­cause the dis­patcher does re­l­at­ively little work and does not block, it po­ten­tially can dis­patch mes­sages as fast as the mes­saging system can feed them and thus avoids be­com­ing a bot­tle­neck.

                                                                                                                                                                                                                该模式是反应器模式[ POSA2 ]的一个更简单的、特定于消息传递的版本,其中消息调度程序是反应器,消息执行者是具体事件处理程序。消息通道充当同步事件多路分解器,使调度程序一次可以使用一条消息。消息本身就像句柄,但简单得多。真正的句柄往往是对资源数据的引用,而消息通常直接包含数据。(但是,消息不必直接存储数据。如果消息的数据存储在外部,并且消息是 Claim Check,则消息包含对数据的引用,这更像是反应器句柄。)不同类型的句柄选择不同类型的具体事件处理程序,而消息通道是数据类型通道,因此所有消息(句柄)都属于同一类型,并且通常只有一种类型的具体事件处理程序。

                                                                                                                                                                                                                This pat­tern is a sim­pler, mes­saging-spe­cific ver­sion of the Re­actor pat­tern [POSA2], where the mes­sage dis­patcher is a Re­actor and the mes­sage per­formers are Con­crete Event Hand­lers. The Mes­sage Chan­nel acts as the Syn­chron­ous Event De­mul­ti­plexer, making the mes­sages avail­able to the dis­patcher one at a time. The mes­sages them­selves are like Handles, but much sim­pler. A true handle tends to be a ref­er­ence to a re­source's data, whereas a mes­sage usu­ally con­tains the data dir­ectly. (How­ever, the mes­sage does not have to store the data dir­ectly. If a mes­sage's data is stored ex­tern­ally and the mes­sage is a Claim Check, then the mes­sage con­tains a ref­er­ence to the data, which is more like a Re­actor handle.) Dif­fer­ent types of handles select dif­fer­ent types of con­crete event hand­lers, whereas a Mes­sage Chan­nel is a Data­type Chan­nel, so all of the mes­sages (handles) are of the same type, and there is typ­ic­ally only one type of con­crete event hand­ler.

                                                                                                                                                                                                                数据类型通道设计一个通道,以便所有消息都属于同一类型,并且所有消费者都处理该类型的消息,而反应堆模式指出了使用消息调度程序在同一通道上支持多种数据类型并使用特定于类型的方式处理它们的机会。表演者。每条消息必须指定其类型;调度程序检测消息的类型并将其调度到特定于类型的执行者进行处理。通过这种方式,具有专门执行者的调度程序可以充当数据类型通道的替代方案以及选择性消费者的专门实现

                                                                                                                                                                                                                Whereas Data­type Chan­nel designs a chan­nel so that all mes­sages are of the same type and all con­sumers pro­cess mes­sages of that type, the Re­actor pat­tern points out an op­por­tun­ity to use Mes­sage Dis­patcher to sup­port mul­tiple data­types on the same chan­nel and pro­cess them with type-spe­cific per­formers. Each mes­sage must spe­cify its type; the dis­patcher de­tects the mes­sage's type and dis­patches it to a type-spe­cific per­former for pro­cess­ing. In this way, a dis­patcher with spe­cial­ized per­formers can act as an al­tern­at­ive to Data­type Chan­nels and as a spe­cial­ized im­ple­ment­a­tion of Se­lect­ive Con­sumers.

                                                                                                                                                                                                                消息调度程序和竞争消费者之间的区别之一是跨多个应用程序分发的能力。尽管一组竞争消费者可能分布在多个进程(例如,应用程序)中,但一组执行者通常都在与调度程序相同的进程中运行(即使它们在不同的线程中运行)。如果执行者与其调度程序运行在不同的进程中,则调度程序必须以分布式远程过程调用方式与执行者进行通信,而这正是消息传递首先要避免的。

                                                                                                                                                                                                                One dif­fer­ence between Mes­sage Dis­patcher and Com­pet­ing Con­sumers is the abil­ity to dis­trib­ute across mul­tiple ap­plic­a­tions. Whereas a set of Com­pet­ing Con­sumers may be dis­trib­uted among mul­tiple pro­cesses (e.g., ap­plic­a­tions), a set of per­formers typ­ic­ally all run in the same pro­cess as the dis­patcher (even if they run in dif­fer­ent threads). If a per­former were run­ning in a dif­fer­ent pro­cess from its dis­patcher, the dis­patcher would have to com­mu­nic­ate with the per­former in a dis­trib­uted, Remote Pro­ced­ure In­voc­a­tion manner, which is ex­actly what Mes­saging in­tends to avoid in the first place.

                                                                                                                                                                                                                由于调度程序是单个消费者,因此它可以与点对点通道发布-订阅通道配合良好。通过点对点消息传递,调度程序可以成为竞争消费者的合适替代方案;如果消息传递系统不能很好地处理多个消费者,或者如果跨不同消息传递系统实现的多个消费者的处理不一致,则这种替代方案可能是更可取的。

                                                                                                                                                                                                                Since a dis­patcher is a single con­sumer, it works fine with both Point-to-Point Chan­nels and Pub­lish-Sub­scribe Chan­nels. With point-to-point mes­saging, a dis­patcher can be a suit­able al­tern­at­ive to Com­pet­ing Con­sumers; this al­tern­at­ive may be prefer­able if the mes­saging system handles mul­tiple con­sumers badly or if hand­ling of mul­tiple con­sumers across dif­fer­ent mes­saging system im­ple­ment­a­tions is in­con­sist­ent.

                                                                                                                                                                                                                调度程序使执行者的工作方式与事件驱动的消费者非常相似,即使调度程序本身可以是事件驱动的或轮询消费者。因此,将调度程序实现为事务客户端的一部分可能很困难。如果客户端是事务性的,则理想情况下,调度程序应允许执行者在完成事务之前处理消息。然后,只有当执行者成功时,调度程序才应提交事务。如果执行者无法处理消息,则调度程序应回滚事务。由于每个执行者可能需要回滚其单独的消息,因此调度程序需要每个执行者的会话,并且必须使用该执行者的会话来接收执行者的消息并完成其事务。由于事件驱动的消费者通常不能很好地与事务性客户端一起工作,因此调度程序不应该是事件驱动的消费者,而应该是轮询消费者

                                                                                                                                                                                                                A dis­patcher makes the per­formers work much like Event-Driven Con­sumers, even though the dis­patcher itself could be event-driven or a Polling Con­sumer. As such, im­ple­ment­ing a dis­patcher as part of a Trans­ac­tional Client can be dif­fi­cult. If the client is trans­ac­tional, ideally the dis­patcher should allow the per­former to pro­cess a mes­sage before com­plet­ing the trans­ac­tion. Then, only if the per­former is suc­cess­ful should the dis­patcher commit the trans­ac­tion. If the per­former fails to pro­cess the mes­sage, the dis­patcher should roll back the trans­ac­tion. Since each per­former may need to roll back its in­di­vidual mes­sage, the dis­patcher needs a ses­sion for each per­former and must use that per­former's ses­sion to re­ceive the per­former's mes­sage and com­plete its trans­ac­tion. Since Event-Driven Con­sumers often do not work well with Trans­ac­tional Clients, the dis­patcher should not be an Event-Driven Con­sumer, but rather should be a Polling Con­sumer.

                                                                                                                                                                                                                将执行者实现为事件驱动的消费者会很有帮助。在 JMS 中,这意味着将执行者实现为MessageListener 。消息监听器有一个方法,onMessage(Message);它接受消息并执行任何必要的处理。这在调度员和执行者之间形成了清晰的分离。同样,在 .NET 中,执行者应该是ReceiveCompletedEventHandler 委托,即使调度程序不会真正发出ReceiveCompleted 事件。然而,这些事件驱动的方法可能与在其自己的线程中运行执行者所需的 API 不兼容。

                                                                                                                                                                                                                It can be help­ful to im­ple­ment per­formers as Event-Driven Con­sumers. In JMS, this means im­ple­ment­ing the per­former as a Mes­sageL­istener. A mes­sage listener has one method, on­Mes­sage(Mes­sage); it ac­cepts a mes­sage and per­forms whatever pro­cess­ing ne­ces­sary. This forms a clean sep­ar­a­tion between the dis­patcher and the per­former. Like­wise, in .NET, the per­former should be a Re­ceive­Com­plete­dE­ventHand­ler del­eg­ate, even though the dis­patcher will not really issue Re­ceive­Com­pleted events. How­ever, these event-driven ap­proaches may not be com­pat­ible with the API ne­ces­sary to run a per­former in its own thread.

                                                                                                                                                                                                                为了避免实现您自己的消息调度程序,考虑在数据类型通道上使用竞争消费者或使用选择性消费者消息调度程序可以是轮询消费者事件驱动消费者消息调度程序并不能成为非常好的事务客户端

                                                                                                                                                                                                                To avoid the effort of im­ple­ment­ing your own Mes­sage Dis­patcher, con­sider in­stead using Com­pet­ing Con­sumers on a Data­type Chan­nel or using Se­lect­ive Con­sumers. A Mes­sage Dis­patcher can be a Polling Con­sumer or an Event-Driven Con­sumer. A Mes­sage Dis­patcher does not make a very good Trans­ac­tional Client.

                                                                                                                                                                                                                示例: .NET 调度程序

                                                                                                                                                                                                                Ex­ample: .NET Dis­patcher

                                                                                                                                                                                                                通常,消息调度程序将消息调度给执行者(请参阅 Java 示例)。.NET 提供了另一种选择:调度程序可以使用 Peek 来检测消息并获取其消息 ID,然后将消息 ID(不是完整消息)调度给执行者。然后,执行者使用ReceiveById 来消费已分配给它的特定消息。通过这种方式,每个执行者不仅可以负责处理消息,还可以负责消费消息,这可以帮助解决并发问题,特别是当消费者是事务性客户端时

                                                                                                                                                                                                                Usu­ally, a Mes­sage Dis­patcher dis­patches mes­sages to the per­formers (see the Java ex­ample). .NET provides an­other option: The dis­patcher can use Peek to detect a mes­sage and get its mes­sage ID, then dis­patch the mes­sage ID (not the full mes­sage) to the per­former. The per­former then uses Re­ceive­ById to con­sume the par­tic­u­lar mes­sage it has been as­signed. In this way, each per­former can take re­spons­ib­il­ity not just for pro­cess­ing the mes­sage but for con­sum­ing it as well, which can help with con­cur­rency issues, es­pe­cially when the con­sumers are Trans­ac­tional Cli­ents.



                                                                                                                                                                                                                示例: 简单的 Java 调度程序

                                                                                                                                                                                                                Ex­ample: Simple Java Dis­patcher

                                                                                                                                                                                                                这是一个如何用 Java 实现调度程序和执行程序的简单示例。更复杂的调度程序实现可能会汇集多个执行者,跟踪当前可用于处理消息的执行者,并使用线程池。这个简单的示例跳过了这些细节,但确实在自己的线程中运行每个执行者,以便它们可以同时运行。

                                                                                                                                                                                                                This is a simple ex­ample of how to im­ple­ment a dis­patcher and per­former in Java. A more soph­ist­ic­ated dis­patcher im­ple­ment­a­tion might pool sev­eral per­formers, keep track of which ones are cur­rently avail­able to pro­cess mes­sages, and make use of a thread pool. This simple ex­ample skips those de­tails but does run each per­former in its own thread so that they can run con­cur­rently.

                                                                                                                                                                                                                控制调度程序(未显示)的驱动程序/管理器将重复运行receiveSync() 。每次,调度程序都会receive()下一条消息,实例化一个新的执行者实例来处理该消息,然后自己。

                                                                                                                                                                                                                The driver/man­ager that con­trols the dis­patcher (not shown) will run re­ceive­Sync() re­peatedly. Each time, the dis­patcher will re­ceive() the next mes­sage, in­stan­ti­ate a new per­former in­stance to pro­cess the mes­sage, and then start the per­former in its own thread.

                                                                                                                                                                                                                
                                                                                                                                                                                                                导入 javax.jms.Connection;
                                                                                                                                                                                                                导入 javax.jms.Destination;
                                                                                                                                                                                                                导入 javax.jms.JMSException;
                                                                                                                                                                                                                导入javax.jms.Message;
                                                                                                                                                                                                                导入 javax.jms.MessageConsumer;
                                                                                                                                                                                                                导入javax.jms.Session;
                                                                                                                                                                                                                导入 javax.naming.NamingException;
                                                                                                                                                                                                                
                                                                                                                                                                                                                公共类消息调度器{
                                                                                                                                                                                                                
                                                                                                                                                                                                                    MessageConsumer消费者;
                                                                                                                                                                                                                    int 下一个ID = 1;
                                                                                                                                                                                                                
                                                                                                                                                                                                                    受保护的消息调度程序(){
                                                                                                                                                                                                                        极好的();
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                
                                                                                                                                                                                                                    公共静态 MessageDispatcher newDispatcher(连接
                                                                                                                                                                                                                图形/ccc.gif联系,
                                                                                                                                                                                                                      字符串队列名称)
                                                                                                                                                                                                                        抛出 JMSException、NamingException {
                                                                                                                                                                                                                        MessageDispatcher 调度程序 = new MessageDispatcher();
                                                                                                                                                                                                                        调度程序.initialize(连接, 队列名称);
                                                                                                                                                                                                                        返回调度员;
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                
                                                                                                                                                                                                                    protected void 初始化(连接连接,字符串
                                                                                                                                                                                                                图形/ccc.gif队列名称)
                                                                                                                                                                                                                        抛出 JMSException、NamingException {
                                                                                                                                                                                                                        会话会话 = connection.createSession(false, 会话
                                                                                                                                                                                                                图形/ccc.gif.AUTO_ACKNOWLEDGE);
                                                                                                                                                                                                                        目标调度程序队列 = JndiUtil.getDestination
                                                                                                                                                                                                                图形/ccc.gif(队列名称);
                                                                                                                                                                                                                        消费者 = session.createConsumer(dispatcherQueue);
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                
                                                                                                                                                                                                                    公共无效 receiveSync() 抛出 JMSException {
                                                                                                                                                                                                                        消息消息=consumer.receive();
                                                                                                                                                                                                                        表演者 表演者 = new Performer(nextID++, message);
                                                                                                                                                                                                                        新线程(执行者).start();
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                }
                                                                                                                                                                                                                
                                                                                                                                                                                                                
                                                                                                                                                                                                                import javax.jms.Con­nec­tion;
                                                                                                                                                                                                                import javax.jms.Des­tin­a­tion;
                                                                                                                                                                                                                import javax.jms.JM­SEx­cep­tion;
                                                                                                                                                                                                                import javax.jms.Mes­sage;
                                                                                                                                                                                                                import javax.jms.Mes­sage­Con­sumer;
                                                                                                                                                                                                                import javax.jms.Ses­sion;
                                                                                                                                                                                                                import javax.naming.NamingEx­cep­tion;
                                                                                                                                                                                                                
                                                                                                                                                                                                                public class Mes­sageDis­patcher {
                                                                                                                                                                                                                
                                                                                                                                                                                                                    Mes­sage­Con­sumer con­sumer;
                                                                                                                                                                                                                    int nextID = 1;
                                                                                                                                                                                                                
                                                                                                                                                                                                                    pro­tec­ted Mes­sageDis­patcher() {
                                                                                                                                                                                                                        super();
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                
                                                                                                                                                                                                                    public static Mes­sageDis­patcher newDis­patcher(Con­nec­tion
                                                                                                                                                                                                                 con­nec­tion,
                                                                                                                                                                                                                      String queueName)
                                                                                                                                                                                                                        throws JM­SEx­cep­tion, NamingEx­cep­tion {
                                                                                                                                                                                                                        Mes­sageDis­patcher dis­patcher = new Mes­sageDis­patcher();
                                                                                                                                                                                                                        dis­patcher.ini­tial­ize(con­nec­tion, queueName);
                                                                                                                                                                                                                        return dis­patcher;
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                
                                                                                                                                                                                                                    pro­tec­ted void ini­tial­ize(Con­nec­tion con­nec­tion, String
                                                                                                                                                                                                                 queueName)
                                                                                                                                                                                                                        throws JM­SEx­cep­tion, NamingEx­cep­tion {
                                                                                                                                                                                                                        Ses­sion ses­sion = con­nec­tion.cre­ateSes­sion(false, Ses­sion
                                                                                                                                                                                                                .AUTO_AC­KNOW­LEDGE);
                                                                                                                                                                                                                        Des­tin­a­tion dis­patch­er­Queue = Jn­di­Util.get­Des­tin­a­tion
                                                                                                                                                                                                                (queueName);
                                                                                                                                                                                                                        con­sumer = ses­sion.cre­ate­Con­sumer(dis­patch­er­Queue);
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                
                                                                                                                                                                                                                    public void re­ceive­Sync() throws JM­SEx­cep­tion {
                                                                                                                                                                                                                        Mes­sage mes­sage = con­sumer.re­ceive();
                                                                                                                                                                                                                        Per­former per­former = new Per­former(nextID++, mes­sage);
                                                                                                                                                                                                                        new Thread(per­former).start();
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                }
                                                                                                                                                                                                                

                                                                                                                                                                                                                执行必须实现Runnable,以便它可以在自己的线程中运行。runnable 的run()方法只是调用processMessage()。完成后,执行者就有资格进行垃圾收集。

                                                                                                                                                                                                                The per­former must im­ple­ment Run­nable so that it can run in its own thread. The run­nable's run() method simply calls pro­cess­Mes­sage(). When this is com­plete, the per­former be­comes eli­gible for garbage col­lec­tion.

                                                                                                                                                                                                                
                                                                                                                                                                                                                导入 javax.jms.JMSException;
                                                                                                                                                                                                                导入javax.jms.Message;
                                                                                                                                                                                                                
                                                                                                                                                                                                                公共类 Performer 实现 Runnable {
                                                                                                                                                                                                                
                                                                                                                                                                                                                    私有 int 执行者ID;
                                                                                                                                                                                                                    私信留言;
                                                                                                                                                                                                                
                                                                                                                                                                                                                    公共表演者(int id,消息消息){
                                                                                                                                                                                                                        表演者ID = id;
                                                                                                                                                                                                                        this.message = 消息;
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                
                                                                                                                                                                                                                    公共无效运行(){
                                                                                                                                                                                                                        尝试 {
                                                                                                                                                                                                                            处理消息();
                                                                                                                                                                                                                        } catch (异常 e) {
                                                                                                                                                                                                                            e.printStackTrace();
                                                                                                                                                                                                                        }
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                
                                                                                                                                                                                                                    private void processMessage() 抛出 JMSException,
                                                                                                                                                                                                                图形/ccc.gif中断异常{
                                                                                                                                                                                                                        int id = message.getIntProperty("cust_id");
                                                                                                                                                                                                                
                                                                                                                                                                                                                        System.out.println(System.currentTimeMillis() + ":
                                                                                                                                                                                                                图形/ccc.gif表演者#”
                                                                                                                                                                                                                          + PerformerID + " 开始;消息 ID " + id);
                                                                                                                                                                                                                        线程.sleep(500);
                                                                                                                                                                                                                        System.out.println(System.currentTimeMillis() + ":
                                                                                                                                                                                                                图形/ccc.gif表演者#”
                                                                                                                                                                                                                          + 表演者ID + “正在处理。”);
                                                                                                                                                                                                                        线程.sleep(500);
                                                                                                                                                                                                                        System.out.println(System.currentTimeMillis() + ":
                                                                                                                                                                                                                图形/ccc.gif表演者#”
                                                                                                                                                                                                                          + 表演者ID + “完成。”);
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                }
                                                                                                                                                                                                                
                                                                                                                                                                                                                
                                                                                                                                                                                                                import javax.jms.JM­SEx­cep­tion;
                                                                                                                                                                                                                import javax.jms.Mes­sage;
                                                                                                                                                                                                                
                                                                                                                                                                                                                public class Per­former im­ple­ments Run­nable {
                                                                                                                                                                                                                
                                                                                                                                                                                                                    private int per­formerID;
                                                                                                                                                                                                                    private Mes­sage mes­sage;
                                                                                                                                                                                                                
                                                                                                                                                                                                                    public Per­former(int id, Mes­sage mes­sage) {
                                                                                                                                                                                                                        per­formerID = id;
                                                                                                                                                                                                                        this.mes­sage = mes­sage;
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                
                                                                                                                                                                                                                    public void run() {
                                                                                                                                                                                                                        try {
                                                                                                                                                                                                                            pro­cess­Mes­sage();
                                                                                                                                                                                                                        } catch (Ex­cep­tion e) {
                                                                                                                                                                                                                            e.print­Stack­Trace();
                                                                                                                                                                                                                        }
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                
                                                                                                                                                                                                                    private void pro­cess­Mes­sage() throws JM­SEx­cep­tion,
                                                                                                                                                                                                                 In­ter­rup­te­dEx­cep­tion {
                                                                                                                                                                                                                        int id = mes­sage.getInt­Prop­erty("cust_id");
                                                                                                                                                                                                                
                                                                                                                                                                                                                        System.out.println(System.cur­rent­Time­Mil­lis() + ":
                                                                                                                                                                                                                 Per­former #"
                                                                                                                                                                                                                          + per­formerID + " start­ing; mes­sage ID " + id);
                                                                                                                                                                                                                        Thread.sleep(500);
                                                                                                                                                                                                                        System.out.println(System.cur­rent­Time­Mil­lis() + ":
                                                                                                                                                                                                                 Per­former #"
                                                                                                                                                                                                                          + per­formerID + " pro­cess­ing.");
                                                                                                                                                                                                                        Thread.sleep(500);
                                                                                                                                                                                                                        System.out.println(System.cur­rent­Time­Mil­lis() + ":
                                                                                                                                                                                                                 Per­former #"
                                                                                                                                                                                                                          + per­formerID + " fin­ished.");
                                                                                                                                                                                                                    }
                                                                                                                                                                                                                }
                                                                                                                                                                                                                

                                                                                                                                                                                                                实现一个简单的调度程序和执行程序很容易。主要技巧是使执行者成为可运行的并在自己的线程中运行它。

                                                                                                                                                                                                                Im­ple­ment­ing a simple dis­patcher and per­former is easy. The main trick is to make the per­former a Run­nable and run it in its own thread.



                                                                                                                                                                                                                  选择性消费者

                                                                                                                                                                                                                  Selective Consumer

                                                                                                                                                                                                                  图形/selectiveconsumer_icon.gif

                                                                                                                                                                                                                  应用程序正在使用消息传递。它消耗来自消息通道的消息但它不一定要消耗该通道上的所有消息,而只是其中的一些消息。

                                                                                                                                                                                                                  An ap­plic­a­tion is using Mes­saging. It con­sumes Mes­sages from a Mes­sage Chan­nel, but it does not ne­ces­sar­ily want to con­sume all of the mes­sages on that chan­neljust some of them.

                                                                                                                                                                                                                  消息消费者如何选择它希望接收哪些消息?

                                                                                                                                                                                                                  How can a mes­sage con­sumer select which mes­sages it wishes to re­ceive?



                                                                                                                                                                                                                  默认情况下,如果消息通道只有一个消费者,则该通道上的所有消息都将传递给该消费者。同样,如果通道上有多个竞争消费者,则任何消息都可能发送给任何消费者,并且每条消息都将发送给某个消费者。消费者通常无法选择消费哪些消息;它总是收到下一条消息。

                                                                                                                                                                                                                  By de­fault, if a Mes­sage Chan­nel has only one con­sumer, all Mes­sages on that chan­nel will be de­livered to that con­sumer. Like­wise, if there are mul­tiple Com­pet­ing Con­sumers on the chan­nel, any mes­sage can po­ten­tially go to any con­sumer, and every mes­sage will go to some con­sumer. A con­sumer nor­mally does not get to choose which mes­sages it con­sumes; it always gets whatever mes­sage is next.

                                                                                                                                                                                                                  只要消费者想要接收通道上的任何和所有消息(通常是这种情况),这种行为就可以。然而,当消费者只想消费某些消息时,这是一个问题,因为消费者通常无法控制它在通道上接收哪些消息。为什么消费者只想接收某些消息?考虑一个处理贷款请求消息的应用程序;它可能希望以不同于 100,000 美元以上的方式处理 100,000 美元以下的贷款。一种方法是让应用程序拥有两种不同类型的消费者,一种用于小额贷款,另一种用于大额贷款。然而,由于任何消费者都可以接收任何消息,应用程序如何确保将正确的消息发送给正确的消费者?

                                                                                                                                                                                                                  This be­ha­vior is fine as long as the con­sumer wants to re­ceive any and all mes­sages on the chan­nel, which is nor­mally the case. This is a prob­lem, how­ever, when a con­sumer wants to con­sume only cer­tain mes­sages, be­cause a con­sumer nor­mally has no con­trol over which mes­sages on a chan­nel it re­ceives. Why would a con­sumer want to re­ceive only cer­tain mes­sages? Con­sider an ap­plic­a­tion pro­cess­ing loan re­quest mes­sages; it may want to pro­cess loans for up to $100,000 dif­fer­ently from those over $100,000. One ap­proach would be for the ap­plic­a­tion to have two dif­fer­ent kinds of con­sumers, one for small loans and an­other for big loans. Yet, since any con­sumer can re­ceive any mes­sage, how can the ap­plic­a­tion make sure that the right mes­sages go to the right con­sumer?

                                                                                                                                                                                                                  最简单的方法可能是每个人都消费它收到的任何消息。如果它收到错误类型的消息,它可以以某种方式将该消息传递给适当类型的消费者。不过,这会很困难;消费者实例通常彼此不了解,并且找到尚未忙于处理另一条消息的消费者实例可能很困难。也许当消费者意识到它不需要该消息时,它可以将消息放回到通道上。但随后它可能会再次使用该消息。也许每个消费者都可以获得每条消息的副本,并丢弃不需要的消息。这会起作用,但会导致大量消息重复以及对最终被丢弃的消息的大量浪费处理。

                                                                                                                                                                                                                  The simplest ap­proach might be for each to con­sume whatever mes­sages it gets. If it gets the wrong kind of mes­sage, it could some­how hand that mes­sage to the ap­pro­pri­ate kind of con­sumer. That's going to be dif­fi­cult, though; con­sumer in­stances usu­ally don't know about each other, and find­ing one that isn't already busy pro­cess­ing an­other mes­sage can be dif­fi­cult. Per­haps when the con­sumer real­izes it doesn't want the mes­sage, it could put the mes­sage back on the chan­nel. But then it's likely to just con­sume the mes­sage yet again. Per­haps every con­sumer could get a copy of every mes­sage and just dis­card the ones it doesn't want. This will work but will cause a lot of mes­sage du­plic­a­tion and a lot of wasted pro­cess­ing on mes­sages that are ul­ti­mately dis­carded.

                                                                                                                                                                                                                  也许消息传递系统可以为每种类型的消息定义单独的通道。然后,发送者可以确保在正确的通道上发送每条消息,而接收者可以确保他们从特定通道接收到的消息是所需的类型。然而,这个解决方案不是很动态。接收者可能会在系统运行时更改其选择标准,这将需要定义新的通道并重新分发通道上已有的消息。它还意味着发送者必须知道接收者的选择标准是什么以及这些标准何时改变。标准必须是接收者的属性,而不是通道的属性,并且通道上的消息需要指定它们满足什么标准。

                                                                                                                                                                                                                  Per­haps the mes­saging system could define sep­ar­ate chan­nels for each type of mes­sage. Then, the sender could make sure to send each mes­sage on the proper chan­nel, and the re­ceiv­ers could be sure that the mes­sages they re­ceive off of a par­tic­u­lar chan­nel are the kind de­sired. How­ever, this solu­tion is not very dy­namic. The re­ceiv­ers may change their se­lec­tion cri­teria while the system is run­ning, which would re­quire de­fin­ing new chan­nels and re­dis­trib­ut­ing the mes­sages already on the chan­nels. It also means that the senders must know what the re­ceiv­ers' se­lec­tion cri­teria are and when those cri­teria change. The cri­teria need to be a prop­erty of the re­ceiv­ers, not of the chan­nels, and the mes­sages on the chan­nel need to spe­cify what cri­teria they meet.

                                                                                                                                                                                                                  我们需要一种方法,让符合各种标准的消息都在同一个通道上发送,让消费者能够指定他们感兴趣的标准,并且让每个消费者只接收满足其要求的消息。标准。

                                                                                                                                                                                                                  What is needed is a way for mes­sages fit­ting a vari­ety of cri­teria to all be sent on the same chan­nel, for the con­sumers to be able to spe­cify what cri­teria they're in­ter­ested in, and for each con­sumer to re­ceive only the mes­sages that meet its cri­teria.

                                                                                                                                                                                                                  让消费者成为选择性消费者,它会过滤其通道传递的消息,以便只接收符合其条件的消息。

                                                                                                                                                                                                                  Make the con­sumer a Se­lect­ive Con­sumer, one that fil­ters the mes­sages de­livered by its chan­nel so that it re­ceives only the ones that match its cri­teria.

                                                                                                                                                                                                                  图形/10inf19.gif



                                                                                                                                                                                                                  此过滤过程分为三个部分:

                                                                                                                                                                                                                  There are three parts to this fil­ter­ing pro­cess:

                                                                                                                                                                                                                  1. 指定生产者 在发送消息之前 指定消息的选择值。

                                                                                                                                                                                                                  2. Spe­cify­ing Pro­du­cer Spe­cifies the mes­sage's se­lec­tion value before send­ing it.

                                                                                                                                                                                                                  3. 选择值 消息中指定的一个或多个值,允许消费者决定是否选择该消息。

                                                                                                                                                                                                                  4. Se­lec­tion Value One or more values spe­cified in the mes­sage that allow a con­sumer to decide whether to select the mes­sage.

                                                                                                                                                                                                                  5. 选择性消费者 仅接收符合其选择标准的消息。

                                                                                                                                                                                                                  6. Se­lect­ive Con­sumer Only re­ceives mes­sages that meet its se­lec­tion cri­teria.

                                                                                                                                                                                                                  消息发送者在发送之前指定每条消息的选择值。当消息到达时,选择性消费者会测试消息的选择值,以查看该值是否满足消费者的选择标准。如果是,消费者接收消息并将其传递给应用程序进行处理。

                                                                                                                                                                                                                  The mes­sage sender spe­cifies each mes­sage's se­lec­tion value before send­ing it. When a mes­sage ar­rives, a Se­lect­ive Con­sumer tests the mes­sage's se­lec­tion value to see if the value meets the con­sumer's se­lec­tion cri­teria. If so, the con­sumer re­ceives the mes­sage and passes it to the ap­plic­a­tion for pro­cess­ing.

                                                                                                                                                                                                                  选择性消费序列

                                                                                                                                                                                                                  Se­lect­ive Con­sumer Se­quence

                                                                                                                                                                                                                  图形/10inf20.gif

                                                                                                                                                                                                                  当发送者创建消息时,它也会设置消息的选择值;然后它发送消息。当消息系统传递消息时,选择性消费者测试消息的选择值以确定是否选择该消息。如果消息通过,使用者将接收消息并使用回调将消息传递给应用程序。

                                                                                                                                                                                                                  When the sender cre­ates the mes­sage, it also sets the mes­sage's se­lec­tion value; then it sends the mes­sage. When the mes­saging system de­liv­ers the mes­sage, the Se­lect­ive Con­sumer tests the mes­sage's se­lec­tion value to de­term­ine whether to select the mes­sage. If the mes­sage passes, the con­sumer re­ceives the mes­sage and passes the mes­sage to the ap­plic­a­tion, using a call­back.

                                                                                                                                                                                                                  Selective Consumer通常用于组中,一个消费者过滤一组条件,而另一个消费者过滤另一组条件,依此类推。对于贷款处理示例,一个消费者会选择“金额 < = $100,000”,而另一个消费者会选择“金额 > $100,000”。然后,每个消费者只能获得其感兴趣的贷款类型。

                                                                                                                                                                                                                  Se­lect­ive Con­sumers are often used in groupsone con­sumer fil­ters for one set of cri­teria, while an­other fil­ters for a dif­fer­ent set, and so on. For the loan pro­cess­ing ex­ample, one con­sumer would select "amount < = $100,000" while an­other would select "amount > $100,000." Then, each con­sumer would only get the kinds of loans it is in­ter­ested in.

                                                                                                                                                                                                                  当多个选择性消费者点对点时,它们实际上成为同样具有选择性的竞争消费者。如果两个消费者的标准重叠,并且消息的选择值满足两个消费者的标准,则任一消费者都可以消费该消息。消费者的设计应确保其中至少一个有资格消费每一个有效的选择值。否则,具有不匹配选择值的消息将永远不会被消耗,并且将永远混乱通道(或至少直到发生消息过期为止)。

                                                                                                                                                                                                                  When mul­tiple Se­lect­ive Con­sumers are used with a Point-to-Point Chan­nel, they ef­fect­ively become Com­pet­ing Con­sumers that are also se­lect­ive. If two con­sumers' cri­teria over­lap, and a mes­sage's se­lec­tion value meets both of their cri­teria, either con­sumer can con­sume the mes­sage. Con­sumers should be de­signed to ensure that at least one of them is eli­gible to con­sume every valid se­lec­tion value. Oth­er­wise, a mes­sage with an un­matched se­lec­tion value will never be con­sumed and will clut­ter the chan­nel forever (or at least until Mes­sage Ex­pir­a­tion occurs).

                                                                                                                                                                                                                  当多个选择性消费者与发布-订阅通道一起使用时,每条消息都将传递给每个订阅者,但订阅者将简单地忽略不符合其条件的消息副本。一旦消费者决定忽略消息,消息传递系统就可以丢弃该消息,因为它已成功传递并且永远不会被使用。消息传递系统可以通过甚至不传递它知道消费者会忽略的消息来优化此过程,从而减少必须生成和传输的消息副本的数量。这种丢弃被忽略消息的行为与任何保证交付、持久订阅者无关和/或使用消息过期设置。

                                                                                                                                                                                                                  When mul­tiple Se­lect­ive Con­sumers are used with a Pub­lish-Sub­scribe Chan­nel, each mes­sage will be de­livered to each sub­scriber, but a sub­scriber will simply ignore its copy of a mes­sage that does not fit its cri­teria. Once a con­sumer de­cides to ignore a mes­sage, the mes­saging system can dis­card the mes­sage, since it has been suc­cess­fully de­livered and will never be con­sumed. A mes­saging system can op­tim­ize this pro­cess by not even de­liv­er­ing a mes­sage it knows the con­sumer will ignore, thereby de­creas­ing the number of copies of a mes­sage that must be pro­duced and trans­mit­ted. This be­ha­vior of dis­card­ing ig­nored mes­sages is in­de­pend­ent of whatever Guar­an­teed De­liv­ery, Dur­able Sub­scriber, and/or Mes­sage Ex­pir­a­tion set­tings are used.

                                                                                                                                                                                                                  Selective Consumer使单个通道的行为类似于多个数据类型通道。不同类型的消息可以具有不同的选择值,以便专门用于特定类型的消费者将仅接收该类型的消息。这可以促进使用少量通道发送大量类型。此方法还可以在需要的通道数量超出消息传递系统所能支持的企业中保留通道。

                                                                                                                                                                                                                  Se­lect­ive Con­sumers make a single chan­nel act like mul­tiple Data­type Chan­nels. Dif­fer­ent types of mes­sages can have dif­fer­ent se­lec­tion values so that a con­sumer that is spe­cial­ized for a par­tic­u­lar type will only re­ceive mes­sages of that type. This can fa­cil­it­ate send­ing a large number of types using a small number of chan­nels. This ap­proach can also con­serve chan­nels in an en­ter­prise that re­quires more chan­nels than a mes­saging system can sup­port.

                                                                                                                                                                                                                  竞争性的、选择性的消费者

                                                                                                                                                                                                                  Com­pet­ing, Se­lect­ive Con­sumers

                                                                                                                                                                                                                  图形/10inf21.gif

                                                                                                                                                                                                                  当尝试向某些消费者应用程序隐藏某种类型的消息时,使用Selective Consumer来模拟数据类型通道并不是一个好方法。虽然消息传递系统可以确保只有授权的应用程序成功地从通道接收消息,但它们通常不会授权消费者的选择标准,因此被授权访问该通道的恶意消费者可以通过更改其标准来访问未经授权的消息。需要单独的数据类型通道来安全地锁定应用程序。

                                                                                                                                                                                                                  Using Se­lect­ive Con­sumers to emu­late Data­type Chan­nels is not a good ap­proach when trying to hide mes­sages of a cer­tain type from cer­tain con­sumer ap­plic­a­tions. Whereas a mes­saging system can ensure only au­thor­ized ap­plic­a­tions suc­cess­fully re­ceive mes­sages from a chan­nel, they usu­ally do not au­thor­ize a con­sumer's se­lec­tion cri­teria, so a ma­li­cious con­sumer au­thor­ized to access the chan­nel can gain access to un­au­thor­ized mes­sages by chan­ging its cri­teria. Sep­ar­ate data­type chan­nels are needed to se­curely lock out ap­plic­a­tions.

                                                                                                                                                                                                                  使用选择性消费者的替代方法是使用消息调度程序。选择标准内置于调度程序中,然后调度程序使用它们来确定每条消息的执行者。如果消息不满足执行者的任何标准,调度程序可以将不匹配的消息重新路由到无效消息通道,而不是让它混乱通道或丢弃它。 与消息调度程序和竞争消费者之间的权衡一样,问题实际上是您希望让消息传递系统进行调度还是希望自己实现它。如果消息系统不支持选择性消费者作为一项功能,您别无选择,只能使用Message Dispatcher 自行实现

                                                                                                                                                                                                                  An al­tern­at­ive to using Se­lect­ive Con­sumers is to use a Mes­sage Dis­patcher. The se­lec­tion cri­teria are built into the dis­patcher, which then uses them to de­term­ine the per­former for each mes­sage. If a mes­sage does not meet any of the per­former's cri­teria, rather than leave it clut­ter­ing the chan­nel or dis­card­ing it, the dis­patcher can reroute the un­matched mes­sage to the In­valid Mes­sage Chan­nel. As with the trade-off between Mes­sage Dis­patcher and Com­pet­ing Con­sumers, the ques­tion is really whether you wish to let the mes­saging system do the dis­patch­ing or you want to im­ple­ment it your­self. If a mes­saging system does not sup­port Se­lect­ive Con­sumer as a fea­ture, you have no choice but to im­ple­ment it your­self using a Mes­sage Dis­patcher.

                                                                                                                                                                                                                  正如前面提到的,如果通道上的Selective Consumer都没有与消息的选择器值匹配,则该消息将被忽略,就像该通道没有接收者一样。过程编程中的一个类似问题是 case 语句,其中没有一个 case 与正在测试的值匹配。因此,case 语句可以具有与任何其他 case 都不匹配的值相匹配的默认 case。将这种方法应用于消息传递,为没有匹配消费者的消息创建某种默认消费者似乎很诱人。然而,这样的默认使用者将无法按预期工作,因为它需要一个与所有选择器值匹配的表达式,因此它将与所有其他使用者竞争。相反,要实现默认消费者,请使用消息调度程序针对

                                                                                                                                                                                                                  As men­tioned earlier, if none of the Se­lect­ive Con­sumers on a chan­nel match the mes­sage's se­lector value, the mes­sage will be ig­nored as if the chan­nel has no re­ceiv­ers. A sim­ilar prob­lem in pro­ced­ural pro­gram­ming is a case state­ment where none of the cases match the value being tested. Thus, a case state­ment can have a de­fault case that matches values that aren't matched by any other case. Ap­ply­ing this ap­proach to mes­saging, it may seem tempt­ing to create some sort of de­fault con­sumer for mes­sages that oth­er­wise have no match­ing con­sumer. Yet, such a de­fault con­sumer will not work as de­sired be­cause it would re­quire an ex­pres­sion that matches all se­lector values, so it would com­pete with all other con­sumers. In­stead, to im­ple­ment a de­fault con­sumer, use a Mes­sage Dis­patcher that im­ple­ments a case state­ment with a de­fault option for un­handled cases that uses the de­fault con­sumer.

                                                                                                                                                                                                                  选择性消费者的另一个替代方案是消息过滤器。他们实现了大致相同的目标,但方式不同。使用选择性消费者,所有消息都会传递给接收者,但每个接收者都会忽略不需要的消息。消息过滤器位于发送方的通道和接收方的通道之间,仅将所需的消息从发送方的通道传输到接收方的通道。因此,不需要的消息甚至永远不会传递到接收者的通道,因此接收者没有什么可以忽略的。消息过滤器对于删除接收者不想要的消息非常有用。选择性消费者当一个接收者想要忽略某些消息但其他接收者想要接收这些消息时,s 非常有用。

                                                                                                                                                                                                                  An­other al­tern­at­ive to Se­lect­ive Con­sumers is Mes­sage Fil­ters. They ac­com­plish much the same goal, but in dif­fer­ent ways. With a Se­lect­ive Con­sumer, all of the mes­sages are de­livered to the re­ceiv­ers, but each re­ceiver ig­nores un­wanted mes­sages. A Mes­sage Filter sits between a chan­nel from the sender and a chan­nel to the re­ceiver and only trans­fers de­sired mes­sages from the sender's chan­nel to the re­ceiver's. Thus, un­wanted mes­sages are never even de­livered to the re­ceiver's chan­nel, so the re­ceiver has noth­ing to ignore. Mes­sage Filter is useful for get­ting rid of mes­sages that no re­ceiver wants. Se­lect­ive Con­sumers are useful when one re­ceiver wants to ignore cer­tain mes­sages but other re­ceiv­ers want to re­ceive those mes­sages.

                                                                                                                                                                                                                  另一个值得考虑的替代方案是基于内容的路由器。这种类型的路由器就像过滤器一样,确保通道仅获取接收者想要的消息,这可以提高安全性并提高消费者的性能。然而,选择性消费者更加灵活,因为每个过滤选项只需要一个新的消费者(在系统运行时很容易创建),而每个新选项都带有一个基于内容路由器需要一个新的输出通道(在系统运行时创建和使用并不那么容易)以及新通道的新消费者。考虑一项要求变更,您希望以不同于小型和大型贷款的方式处理中型贷款(50,000 美元到 150,000 美元)。使用基于内容的路由器,您需要为中等贷款创建一个新渠道,以及该新渠道上的消费者,并调整路由器分离贷款的方式。您还需要担心更改生效后会发生什么,因为一些已经路由到原始通道的消息可能尚未被消费,现在可能位于错误的通道上。与选择性消费者,您只需将两种类型的消费者(小于 100,000 美元和大于 100,000 美元)替换为三种类型(小于 50,000 美元、50,000 美元至 150,000 美元和大于 150,000 美元)。基于内容的路由器是一种更加静态的方法,而选择性消费者则更加动态。

                                                                                                                                                                                                                  An­other al­tern­at­ive to con­sider is Con­tent-Based Router. This type of router, like a filter, makes sure that a chan­nel gets only the mes­sages that the re­ceiv­ers want, which can in­crease se­cur­ity and in­crease the per­form­ance of the con­sumers. Se­lect­ive Con­sumer is more flex­ible, how­ever, be­cause each fil­ter­ing option simply re­quires a new con­sumer (which is easy to create while the system is run­ning), whereas each new option with a Con­tent-Based Router re­quires a new output chan­nel (which is not so easy to create and use while the system is run­ning) as well as a new con­sumer for the new chan­nel. Con­sider a re­quire­ments change where you want to pro­cess medium-size loans ($50,000 to $150,000) dif­fer­ently from small and large loans. With Con­tent-Based Router, you need to create a new chan­nel for medium loans, as well as a con­sumer on that new chan­nel, and adjust the way the router sep­ar­ates loans. You also need to worry about what hap­pens when the change takes effect, be­cause some mes­sages that have already been routed onto the ori­ginal chan­nels may not have been con­sumed yet and may now be on the wrong chan­nel. With Se­lect­ive Con­sumer, you just re­place the two types of con­sumers (less than $100,000 and greater than $100,000) with three types (less than $50,000, $50,000 to $150,000, and greater than $150,000). Con­tent-Based Router is a much more static ap­proach, whereas Se­lect­ive Con­sumer can be much more dy­namic.

                                                                                                                                                                                                                  理想情况下,消息的选择值应在其标头中指定,而不是其正文,以便选择性消费者可以处理该值,而无需解析(并知道如何解析)消息正文。

                                                                                                                                                                                                                  Ideally, a mes­sage's se­lec­tion value should be spe­cified in its header, not its body, so that a Se­lect­ive Con­sumer can pro­cess the value without having to parse (and know how to parse) the mes­sage's body.

                                                                                                                                                                                                                  Selective Consumer使单个通道的行为类似于多个数据类型通道。它们允许消息可供其他接收者使用,而消息过滤器可防止将不需要的消息传递给任何接收者,并且它们可以比基于内容的路由器更动态地使用。选择性消费者可以实现为轮询消费者或事件驱动消费者,并且可以是事务。要自己实现过滤行为,请使用消息调度程序

                                                                                                                                                                                                                  Se­lect­ive Con­sumer s make a single chan­nel act like mul­tiple Data­type Chan­nels. They allow mes­sages to be avail­able for other re­ceiv­ers, whereas Mes­sage Filter pre­vents un­wanted mes­sages from being de­livered to any re­ceiver, and they can be used more dy­nam­ic­ally than a Con­tent-Based Router. A Se­lect­ive Con­sumer can be im­ple­men­ted as a Polling Con­sumer or Event-Driven Con­sumer and can be part of a Trans­ac­tional Client. To im­ple­ment the fil­ter­ing be­ha­vior your­self, use a Mes­sage Dis­patcher.

                                                                                                                                                                                                                  示例: 分离类型

                                                                                                                                                                                                                  Ex­ample: Sep­ar­at­ing Types

                                                                                                                                                                                                                  渠道数量有限的股票交易系统可能需要使用一个渠道进行报价和交易。执行报价的接收者与交易的接收者非常不同,因此正确的接收者需要确保使用正确的消息。发送者将报价消息上的选择器值设置为 QUOTE,并且报价的选择性消费者将仅消费具有该选择器值的消息。贸易消息将具有其发送者和接收者将使用的自己的 TRADE 选择器值。这样,两种消息类型可以成功地共享单个通道。

                                                                                                                                                                                                                  A stock trad­ing system with a lim­ited number of chan­nels might need to use one chan­nel for both quotes and trades. The re­ceiver for per­form­ing a quote is very dif­fer­ent from that for trad­ing, so the right re­ceiver needs to be sure to con­sume the right mes­sage. The sender would set the se­lector value on a quote mes­sage to QUOTE, and the Se­lect­ive Con­sumer for quotes would con­sume only mes­sages with that se­lector value. Trade mes­sages would have their own TRADE se­lector value that their senders and re­ceiv­ers would use. In this way, two mes­sage types can suc­cess­fully share a single chan­nel.



                                                                                                                                                                                                                  示例: JMS 消息选择器

                                                                                                                                                                                                                  Ex­ample: JMS Mes­sage Se­lector

                                                                                                                                                                                                                  在 JMS 中,可以使用消息选择器创建字符串MessageConsumer( QueueReceiver或 TopicSubscriber),该消息选择器字符串根据属性值 [JMS 1.1]、[ Hapner]过滤消息。 首先,发送者在消息中设置接收者可以过滤的属性值:

                                                                                                                                                                                                                  In JMS, a Mes­sage­Con­sumer (QueueRe­ceiver or Top­ic­Sub­scriber) can be cre­ated with a mes­sage se­lector string that fil­ters mes­sages based on their prop­erty values [JMS 1.1], [Hapner]. First, a sender sets the value of a prop­erty in the mes­sage that the re­ceiver could filter by:

                                                                                                                                                                                                                  Session session = // 获取会话
                                                                                                                                                                                                                  TextMessage 消息 = session.createTextMessage();
                                                                                                                                                                                                                  message.setText("<quote>SUNW</quote>");
                                                                                                                                                                                                                  message.setStringProperty("req_type", "quote");
                                                                                                                                                                                                                  目的地目的地= //获取目的地
                                                                                                                                                                                                                  MessageProducer 生产者 = session.createProducer(destination);
                                                                                                                                                                                                                  生产者.发送(消息);
                                                                                                                                                                                                                  
                                                                                                                                                                                                                  Ses­sion ses­sion = // get the ses­sion
                                                                                                                                                                                                                  Text­Mes­sage mes­sage = ses­sion.cre­at­e­Text­Mes­sage();
                                                                                                                                                                                                                  mes­sage.set­Text("<quote>SUNW</quote>");
                                                                                                                                                                                                                  mes­sage.set­String­Prop­erty("re­q_­type", "quote");
                                                                                                                                                                                                                  Des­tin­a­tion des­tin­a­tion = //get the des­tin­a­tion
                                                                                                                                                                                                                  Mes­sage­Pro­du­cer pro­du­cer = ses­sion.cre­ate­Pro­du­cer(des­tin­a­tion);
                                                                                                                                                                                                                  pro­du­cer.send(mes­sage);
                                                                                                                                                                                                                  

                                                                                                                                                                                                                  其次,接收者设置其消息选择器来过滤该值:

                                                                                                                                                                                                                  Second, a re­ceiver sets its mes­sage se­lector to filter for that value:

                                                                                                                                                                                                                  Session session = // 获取会话
                                                                                                                                                                                                                  目的地目的地= //获取目的地
                                                                                                                                                                                                                  字符串选择器 = "req_type = 'quote'";
                                                                                                                                                                                                                  消息消费者消费者 =
                                                                                                                                                                                                                      session.createConsumer(目的地, 选择器);
                                                                                                                                                                                                                  
                                                                                                                                                                                                                  Ses­sion ses­sion = // get the ses­sion
                                                                                                                                                                                                                  Des­tin­a­tion des­tin­a­tion = //get the des­tin­a­tion
                                                                                                                                                                                                                  String se­lector = "re­q_­type = 'quote'";
                                                                                                                                                                                                                  Mes­sage­Con­sumer con­sumer =
                                                                                                                                                                                                                      ses­sion.cre­ate­Con­sumer(des­tin­a­tion, se­lector);
                                                                                                                                                                                                                  

                                                                                                                                                                                                                  此接收方将忽略其请求类型属性未设置为引用的所有消息,就好像这些消息根本从未传递到目的地一样。

                                                                                                                                                                                                                  This re­ceiver will ignore all mes­sages whose re­quest type prop­erty is not set to quote as if those mes­sages were never de­livered to the des­tin­a­tion at all.



                                                                                                                                                                                                                  示例: .NET Peek、ReceiveById 和 ReceiveByCorrelationId

                                                                                                                                                                                                                  Ex­ample: .NET Peek, Re­ceive­ById, and Re­ceiveBy­Cor­rel­a­tionId

                                                                                                                                                                                                                  在 .NET 中,MessageQueue.Receive本身不支持 JMS 样式的消息选择器。相反,接收者可以做的是使用MessageQueue.Peek来查看消息。如果它满足所需的条件,则可以使用MessageQueue.Receive从队列中读取它。不过,这可能工作得不太可靠,因为Receive调用返回的消息不一定与所查看的消息相同。因此,使用ReceiveById ,消费者由此指定其希望接收的消息的 ID 属性值(而不是指定 Receive),以确保获取与所查看的消息相同的消息。

                                                                                                                                                                                                                  In .NET, Mes­sageQueue.Re­ceive does not sup­port JMS-style mes­sage se­lect­ors per se. Rather, what a re­ceiver can do is use Mes­sageQueue.Peek to look at a mes­sage. If it meets the de­sired cri­teria, then it can use Mes­sageQueue.Re­ceive to read it from the queue. This may not work very re­li­ably, though, since the mes­sage re­turned by the Re­ceive call may not ne­ces­sar­ily be the same mes­sage that was peeked. Thus, use Re­ceive­ById, whereby the con­sumer spe­cifies the ID prop­erty value of the mes­sage it wishes to re­ceive (in­stead of spe­cify­ing Re­ceive) to ensure get­ting the same mes­sage that was peeked.

                                                                                                                                                                                                                  .NET 中的另一个选项是ReceiveByCorrelationId方法,使用者可以使用该方法指定其想要接收的消息的CorrelationId 属性值。特定请求消息的发送者可以使用ReceiveByCorrelationId 来接收特定于该请求的回复消息(请参阅请求回复和相关标识符)

                                                                                                                                                                                                                  An­other option in .NET is the Re­ceiveBy­Cor­rel­a­tionId method with which the con­sumer spe­cifies the Cor­rel­a­tionId prop­erty value of the mes­sage it wants to re­ceive. A sender of a par­tic­u­lar re­quest mes­sage can use Re­ceiveBy­Cor­rel­a­tionId to re­ceive the reply mes­sage spe­cific to that re­quest (see Re­quest-Reply and Cor­rel­a­tion Iden­ti­fier ).



                                                                                                                                                                                                                    持久订阅者

                                                                                                                                                                                                                    Durable Subscriber

                                                                                                                                                                                                                    图形/durablesubscriber_icon.gif

                                                                                                                                                                                                                    应用程序正在发布-订阅通道上接收消息

                                                                                                                                                                                                                    An ap­plic­a­tion is re­ceiv­ing mes­sages on a Pub­lish-Sub­scribe Chan­nel.

                                                                                                                                                                                                                    订户如何避免在未收听消息时丢失消息?

                                                                                                                                                                                                                    How can a sub­scriber avoid miss­ing mes­sages while it's not listen­ing for them?



                                                                                                                                                                                                                    为什么这甚至是一个问题?将消息添加到通道后,它会一直保留在那里,直到被消耗、过期(请参阅消息过期)或系统崩溃(除非您使用保证传递) 。对于点对点通道上的消息来说确实如此,但发布-订阅通道的工作方式略有不同。

                                                                                                                                                                                                                    Why is this even an issue? Once a mes­sage is added to a chan­nel, it stays there until it is either con­sumed, it ex­pires (see Mes­sage Ex­pir­a­tion ), or the system crashes (unless you're using Guar­an­teed De­liv­ery ). This is true for a mes­sage on a Point-to-Point Chan­nel, but a Pub­lish-Sub­scribe Chan­nel works some­what dif­fer­ently.

                                                                                                                                                                                                                    当消息在发布-订阅通道上发布时,消息传递系统必须将消息传递给每个订阅者。它如何做到这一点是特定于实现的:它可以保留消息,直到尚未收到消息的订阅者列表为空,或者它可能会复制消息并将消息传递给每个订阅者。无论哪种情况,哪些订阅者接收消息完全取决于发布消息时谁订阅了该频道。如果消息发布时接收者没有订阅,那么即使接收者稍后订阅,也不会收到该消息。(还有一个时间问题,当订阅者订阅并且消息“大约”同时在同一频道上发布时,会发生什么情况。订阅者是否收到消息?如何解决这个问题取决于消息系统的实现。为了安全起见,订阅者应确保在发布感兴趣的消息之前订阅。)

                                                                                                                                                                                                                    When a mes­sage is pub­lished on a Pub­lish-Sub­scribe Chan­nel, the mes­saging system must de­liver the mes­sage to each sub­scriber. How it does this is im­ple­ment­a­tion spe­cific: It can keep the mes­sage until the list of sub­scribers that have not re­ceived it is empty, or it might du­plic­ate and de­liver the mes­sage to each sub­scriber. Whatever the case, which sub­scribers re­ceive the mes­sage is com­pletely de­pend­ent upon who is sub­scribed to the chan­nel when the mes­sage is pub­lished. If a re­ceiver is not sub­scribed when the mes­sage is pub­lished, even if the re­ceiver sub­scribes an in­stant later, it will not re­ceive that mes­sage. (There is also a timing issue of what hap­pens when a sub­scriber sub­scribes and a mes­sage is pub­lished on the same chan­nel at "about" the same time. Does the sub­scriber re­ceive the mes­sage? How this issue is re­solved de­pends on the mes­saging system's im­ple­ment­a­tion. To be safe, sub­scribers should be sure to sub­scribe before mes­sages of in­terest are pub­lished.)

                                                                                                                                                                                                                    实际上,订阅者通过关闭与频道的连接来取消订阅频道。因此,不需要明确的取消订阅操作;订阅者只需关闭其连接。

                                                                                                                                                                                                                    As a prac­tical matter, a sub­scriber un­sub­scribes from a chan­nel by clos­ing its con­nec­tion to the chan­nel. Thus, no ex­pli­cit un­sub­scribe action is ne­ces­sary; the sub­scriber just closes its con­nec­tion.

                                                                                                                                                                                                                    通常,应用程序更愿意忽略断开连接后发布的消息,因为断开连接意味着应用程序对可能发布的任何内容不感兴趣。例如,销售积木的 B2B/C 应用程序可以订阅买家可以请求积木的频道。如果应用程序停止销售砖块,或者暂时没有砖块,它可能会决定断开与通道的连接,以避免收到无论如何都无法满足的请求。

                                                                                                                                                                                                                    Often, an ap­plic­a­tion prefers to ignore mes­sages pub­lished after it dis­con­nects, be­cause being dis­con­nec­ted means that the ap­plic­a­tion is un­in­ter­ested in whatever may be pub­lished. For ex­ample, a B2B/C ap­plic­a­tion selling bricks may sub­scribe to a chan­nel where buyers can re­quest bricks. If the ap­plic­a­tion stops selling bricks, or is tem­por­ar­ily out of bricks, it may decide to dis­con­nect from the chan­nel to avoid re­ceiv­ing re­quests it cannot ful­fill anyway.

                                                                                                                                                                                                                    然而,这种行为可能是不利的,因为“你打瞌睡,你就放松了”的方法可能会导致应用程序错过它需要的消息。如果应用程序崩溃或必须停止进行维护,它可能想知道在不运行时错过了哪些消息。消息传递的整体理念是即使发送者和接收者应用程序和网络不同时工作,也使通信可靠。

                                                                                                                                                                                                                    Yet this be­ha­vior can be dis­ad­vant­age­ous, be­cause the "you snooze, you loose" ap­proach can cause an ap­plic­a­tion to miss mes­sages it needs. If an ap­plic­a­tion crashes or must be stopped for main­ten­ance, it may want to know what mes­sages it missed while it wasn't run­ning. The whole idea of mes­saging is to make com­mu­nic­a­tion re­li­able even if the sender and re­ceiver ap­plic­a­tions and net­work aren't all work­ing at the same time.

                                                                                                                                                                                                                    因此,有时应用程序会断开连接,因为它们不再需要来自该通道的消息。但有时应用程序必须短时间断开连接,当它们重新连接时,它们希望能够访问在连接失效期间发布的所有消息。订阅者通常处于连接(已订阅)或断开连接(取消订阅)状态,但第三种可能的状态是inactive ,即订阅者已断开连接但仍处于订阅状态,因为它希望接收在断开连接时发布的消息。

                                                                                                                                                                                                                    So, some­times ap­plic­a­tions dis­con­nect be­cause they don't want mes­sages from that chan­nel any­more. But some­times ap­plic­a­tions have to dis­con­nect for a short time, and when they re­con­nect, they want to have access to all of the mes­sages that were pub­lished during the con­nec­tion lapse. A sub­scriber is nor­mally either con­nec­ted (sub­scribed) or dis­con­nec­ted (un­sub­scribed), but a third pos­sible state is in­act­ive, the state of a sub­scriber that is dis­con­nec­ted but still sub­scribed be­cause it wants to re­ceive mes­sages pub­lished while it is dis­con­nec­ted.

                                                                                                                                                                                                                    如果订阅者曾经连接到发布-订阅通道,但在发布消息时断开连接,消息系统如何知道是否为订阅者保存消息,以便订阅者重新连接时可以传递消息?也就是说,消息传递系统如何知道断开连接的订阅者是不活动还是取消订阅?需要有两种订阅,一种订阅在订阅者断开连接时结束,另一种订阅即使在应用程序断开连接时仍然存在,并且仅在应用程序显式取消订阅时才中断。

                                                                                                                                                                                                                    If a sub­scriber was con­nec­ted to a Pub­lish-Sub­scribe Chan­nel but is dis­con­nec­ted when a mes­sage is pub­lished, how does the mes­saging system know whether or not to save the mes­sage for the sub­scriber so that it can de­liver the mes­sage when the sub­scriber re­con­nects? That is, how does the mes­saging system know whether a dis­con­nec­ted sub­scriber is in­act­ive or un­sub­scribed? There needs to be two kinds of sub­scrip­tions, those that end when the sub­scriber dis­con­nects and those that sur­vive even when the ap­plic­a­tion dis­con­nects and are broken only when the ap­plic­a­tion ex­pli­citly un­sub­scribes.

                                                                                                                                                                                                                    默认情况下,订阅仅在其连接期间有效。因此,我们需要另一种类型的订阅,它可以通过变得不活动而在断开连接时继续存在。

                                                                                                                                                                                                                    By de­fault, a sub­scrip­tion lasts only as long as its con­nec­tion. So, what is needed is an­other type of sub­scrip­tion that sur­vives dis­con­nects by be­com­ing in­act­ive.

                                                                                                                                                                                                                    使用持久订阅者可以使消息系统在订阅者断开连接时保存发布的消息。

                                                                                                                                                                                                                    Use a Dur­able Sub­scriber to make the mes­saging system save mes­sages pub­lished while the sub­scriber is dis­con­nec­ted.

                                                                                                                                                                                                                    图形/10inf22.gif



                                                                                                                                                                                                                    持久订阅会保存不活动订阅者的消息,并在订阅者重新连接时传递这些保存的消息。这样,订阅者即使断开连接也不会丢失任何消息。当订阅者处于活动状态(例如,已连接)时,持久订阅对订阅者或消息传送系统的行为没有影响。无论订阅是持久的还是非持久的,连接的订阅者都会采取相同的行为。不同之处在于订阅者断开连接时消息传递系统的行为方式。

                                                                                                                                                                                                                    A dur­able sub­scrip­tion saves mes­sages for an in­act­ive sub­scriber and de­liv­ers these saved mes­sages when the sub­scriber re­con­nects. In this way, a sub­scriber does not lose any mes­sages even though it dis­con­nec­ted. A dur­able sub­scrip­tion has no effect on the be­ha­vior of the sub­scriber or the mes­saging system while the sub­scriber is active (e.g., con­nec­ted). A con­nec­ted sub­scriber acts the same whether its sub­scrip­tion is dur­able or non­dur­able. The dif­fer­ence is in how the mes­saging system be­haves when the sub­scriber is dis­con­nec­ted.

                                                                                                                                                                                                                    持久订阅者只是发布-订阅通道上的订阅者。但是,当订阅者与消息传递系统断开连接时,它就会变为非活动状态,并且消息传递系统将保存在其通道上发布的所有消息,直到它再次变为活动状态。同时,同一频道的其他订阅者可能不会持久;他们是非持久订户。

                                                                                                                                                                                                                    A Dur­able Sub­scriber is simply a sub­scriber on a Pub­lish-Sub­scribe Chan­nel. How­ever, when the sub­scriber dis­con­nects from the mes­saging system, it be­comes in­act­ive and the mes­saging system will save any mes­sages pub­lished on its chan­nel until it be­comes active again. Mean­while, other sub­scribers to the same chan­nel may not be dur­able; they're non­dur­able sub­scribers.

                                                                                                                                                                                                                    持久订阅序列

                                                                                                                                                                                                                    Dur­able Sub­scrip­tion Se­quence

                                                                                                                                                                                                                    图形/10inf23.gif

                                                                                                                                                                                                                    成为订阅者,持久订阅者必须建立对频道的订阅。一旦它关闭连接,它就会变得不活动。当订阅者不活动时,发布者发布消息。如果订阅者是非持久的,它将错过此消息;但由于它是持久的,消息传递系统会为该订阅者保存该消息。当订阅者重新订阅并再次变得活跃时,消息传递系统将传递排队的消息(以及为此订阅者保存的任何其他消息)。订阅者接收消息并处理它(可能将消息委托给应用程序)。一旦订阅者处理完消息,如果它不希望接收更多消息,它将关闭其连接,再次变为非活动状态。

                                                                                                                                                                                                                    To be a sub­scriber, the Dur­able Sub­scriber must es­tab­lish its sub­scrip­tion to the chan­nel. Once it has, when it closes its con­nec­tion, it be­comes in­act­ive. While the sub­scriber is in­act­ive, the pub­lisher pub­lishes a mes­sage. If the sub­scriber were non­dur­able, it would miss this mes­sage; but be­cause it is dur­able, the mes­saging system saves this mes­sage for this sub­scriber. When the sub­scriber re­sub­scribes, be­com­ing active once more, the mes­saging system de­liv­ers the queued mes­sage (and any others saved for this sub­scriber). The sub­scriber re­ceives the mes­sage and pro­cess it (per­haps del­eg­at­ing the mes­sage to the ap­plic­a­tion). Once the sub­scriber is through pro­cess­ing mes­sages, if it does not wish to re­ceive any more mes­sages, it closes its con­nec­tion, be­com­ing in­act­ive again. Since it does not want the mes­saging system to save mes­sages for it any­more, it also un­sub­scribes.

                                                                                                                                                                                                                    一个有趣的考虑是,如果持久订阅者从未取消订阅会发生什么?不活动的持久订阅将继续保留消息,即消息系统将保存所有已发布的消息,直到订阅者重新连接。但如果订阅者长时间不重新连接,保存的消息数量可能会变得过多。消息过期可以帮助缓解这个问题。消息传递系统可能还希望限制可以为非活动订阅保存的消息数量。

                                                                                                                                                                                                                    An in­ter­est­ing con­sid­er­a­tion is, What would happen if a dur­able sub­scriber never un­sub­scribed? The in­act­ive dur­able sub­scrip­tion would con­tinue to retain mes­sagesthat is, the mes­saging system would save all of the pub­lished mes­sages until the sub­scriber re­con­nects. But if the sub­scriber does not re­con­nect for a long time, the number of saved mes­sages can become ex­cess­ive. Mes­sage Ex­pir­a­tion can help al­le­vi­ate this prob­lem. The mes­saging system may also wish to limit the number of mes­sages that can be saved for an in­act­ive sub­scrip­tion.

                                                                                                                                                                                                                    示例: 股票交易

                                                                                                                                                                                                                    Ex­ample: Stock Trad­ing

                                                                                                                                                                                                                    股票交易系统可能使用发布-订阅通道来广播股票价格的变化;每当股票价格发生变化时,就会发布一条消息。一个订阅者可能是一个显示某些股票当前价格的 GUI。另一个订阅者可能是存储某些股票当天交易范围的数据库。

                                                                                                                                                                                                                    A stock trad­ing system might use a Pub­lish-Sub­scribe Chan­nel to broad­cast changes in stocks prices; each time a stocks' price changes, a mes­sage is pub­lished. One sub­scriber might be a GUI that dis­plays the cur­rent prices for cer­tain stocks. An­other sub­scriber might be a data­base that stores the day's trad­ing range for cer­tain stocks.

                                                                                                                                                                                                                    这两个应用程序都应该是价格变化通道的订阅者,以便在股票价格变化时收到通知。GUI 的订阅可能是非持久的,因为它显示的是当前价格。如果 GUI 崩溃并失去与通道的连接,则保存 GUI 无法显示的价格更改就没有意义。相反,价格范围数据库应使用Durable Subscriber 。当它运行时,它可以显示到目前为止的范围。如果它失去连接,当它重新连接时,它可以处理发生的价格变化并根据需要更新范围。

                                                                                                                                                                                                                    Both ap­plic­a­tions should be sub­scribers to the price-change chan­nel so that they're no­ti­fied when a stock's price changes. The GUI's sub­scrip­tion can be non­dur­able be­cause it is dis­play­ing the cur­rent price. If the GUI crashes and loses its con­nec­tion to the chan­nel, there is no point in saving price changes the GUI cannot dis­play. Con­versely, the price range data­base should use a Dur­able Sub­scriber. While it is run­ning, it can dis­play the range thus far. If it loses its con­nec­tion, when it re­con­nects, it can pro­cess the price changes that oc­curred and update the range as ne­ces­sary.



                                                                                                                                                                                                                    示例: JMS 持久订阅

                                                                                                                                                                                                                    Ex­ample: JMS Dur­able Sub­scrip­tion

                                                                                                                                                                                                                    JMS 支持TopicSubscribers [ JMS 1.1 ]、[ Hapner ]的持久订阅。

                                                                                                                                                                                                                    JMS sup­ports dur­able sub­scrip­tions for Top­ic­Sub­scribers [JMS 1.1], [Hapner].

                                                                                                                                                                                                                    持久订阅面临的一项挑战是区分正在重新连接的旧订阅者和全新订阅者。在 JMS 中,持久订阅由三个标准来标识:

                                                                                                                                                                                                                    One chal­lenge with dur­able sub­scrip­tions is dif­fer­en­ti­at­ing between an old sub­scriber that is re­con­nect­ing and a com­pletely new sub­scriber. In JMS, a dur­able sub­scrip­tion is iden­ti­fied by three cri­teria:

                                                                                                                                                                                                                    1. 正在订阅的主题

                                                                                                                                                                                                                    2. The topic being sub­scribed to

                                                                                                                                                                                                                    3. 连接的客户端 ID

                                                                                                                                                                                                                    4. The con­nec­tion's client ID

                                                                                                                                                                                                                    5. 订阅者的订阅名称

                                                                                                                                                                                                                    6. The sub­scriber's sub­scrip­tion name

                                                                                                                                                                                                                    连接的客户端 ID 是其连接工厂的一个属性,该属性是在使用消息传递系统的管理工具创建连接工厂时设置的。每个订阅者的订阅名称必须是唯一的(对于特定主题和客户端 ID)。

                                                                                                                                                                                                                    The con­nec­tion's client ID is a prop­erty of its con­nec­tion fact­ory, which is set when the con­nec­tion fact­ory is cre­ated using the mes­saging system's ad­min­is­tra­tion tool. The sub­scrip­tion name has to be unique for each sub­scriber (for a par­tic­u­lar topic and client ID).

                                                                                                                                                                                                                    使用Session.createDurableSubscriber持久订阅者:

                                                                                                                                                                                                                    A Dur­able Sub­scriber is cre­ated using the Ses­sion.cre­ateD­ur­able­Sub­scriber method:

                                                                                                                                                                                                                    
                                                                                                                                                                                                                    ConnectionFactoryfactory = // 获取工厂
                                                                                                                                                                                                                    // 工厂有客户端 ID
                                                                                                                                                                                                                    连接连接=factory.createConnection();
                                                                                                                                                                                                                    // 连接与工厂具有相同的客户端 ID
                                                                                                                                                                                                                    topic topic = // 获取主题
                                                                                                                                                                                                                    String clientID = connection.getClientID(); // 万一
                                                                                                                                                                                                                    图形/ccc.gif你很好奇
                                                                                                                                                                                                                    String 订阅名称 = "订阅者1"; // 一些UID
                                                                                                                                                                                                                    图形/ccc.gif订阅
                                                                                                                                                                                                                    
                                                                                                                                                                                                                    会话会话=
                                                                                                                                                                                                                        连接.createSession(false, Session.AUTO_ACKNOWLEDGE);
                                                                                                                                                                                                                    TopicSubscriber 订阅者 =
                                                                                                                                                                                                                        session.createDurableSubscriber(主题, 订阅名称);
                                                                                                                                                                                                                    
                                                                                                                                                                                                                    
                                                                                                                                                                                                                    Con­nec­tion­Fact­ory fact­ory = // obtain the fact­ory
                                                                                                                                                                                                                    // the fact­ory has the client ID
                                                                                                                                                                                                                    Con­nec­tion con­nec­tion = fact­ory.cre­ate­Con­nec­tion();
                                                                                                                                                                                                                    // the con­nec­tion has the same client ID as the fact­ory
                                                                                                                                                                                                                    Topic topic = // obtain the topic
                                                                                                                                                                                                                    String cli­en­tID = con­nec­tion.getCli­en­tID(); // just in case
                                                                                                                                                                                                                     you're curi­ous
                                                                                                                                                                                                                    String sub­scrip­tion­Name = "sub­scriber1"; // some UID for the
                                                                                                                                                                                                                     sub­scrip­tion
                                                                                                                                                                                                                    
                                                                                                                                                                                                                    Ses­sion ses­sion =
                                                                                                                                                                                                                        con­nec­tion.cre­ateSes­sion(false, Ses­sion.AUTO_AC­KNOW­LEDGE);
                                                                                                                                                                                                                    Top­ic­Sub­scriber sub­scriber =
                                                                                                                                                                                                                        ses­sion.cre­ateD­ur­able­Sub­scriber(topic, sub­scrip­tion­Name);
                                                                                                                                                                                                                    

                                                                                                                                                                                                                    该订阅者现在处于活动状态。它将在消息发布到主题时接收消息(就像非持久订阅者一样)。要使其处于非活动状态,请将其关闭,如下所示:

                                                                                                                                                                                                                    This sub­scriber is now active. It will re­ceive mes­sages as they are pub­lished to the topic (just like a non­dur­able sub­scriber). To make it in­act­ive, close it, like this:

                                                                                                                                                                                                                    订阅者.close();
                                                                                                                                                                                                                    
                                                                                                                                                                                                                    sub­scriber.close();
                                                                                                                                                                                                                    

                                                                                                                                                                                                                    订阅者现在已断开连接,因此处于非活动状态。发布到其主题的任何消息都将为此订阅者保存并在其重新连接时传递。

                                                                                                                                                                                                                    The sub­scriber is now dis­con­nec­ted and there­fore in­act­ive. Any mes­sages pub­lished to its topic will be saved for this sub­scriber and de­livered when it re­con­nects.

                                                                                                                                                                                                                    要使订阅再次处于活动状态,您必须创建具有相同主题、客户端 ID 和订阅名称的新持久订阅者。代码与之前相同,只是连接工厂、主题和订阅名称必须与之前相同。

                                                                                                                                                                                                                    To make the sub­scrip­tion active again, you must create a new dur­able sub­scriber with the same topic, client ID, and sub­scrip­tion name. The code is the same as before, except that the con­nec­tion fact­ory, topic, and sub­scrip­tion name must be the same as before.

                                                                                                                                                                                                                    由于建立持久订阅和重新连接的代码相同,因此只有消息系统知道该持久订阅是已建立还是新的。一个有趣的结果是,重新连接到订阅的应用程序可能与之前断开连接的应用程序不同。只要新应用程序与旧应用程序使用相同的主题、相同的连接工厂(以及相同的客户端 ID)和相同的订阅名称,消息系统就无法区分这两个应用程序,并将所有消息传递到旧应用程序。在断开连接之前未交付给旧应用程序的新应用程序。

                                                                                                                                                                                                                    Be­cause the code is the same to es­tab­lish a dur­able sub­scrip­tion and to re­con­nect to it, only the mes­saging system knows whether this dur­able sub­scrip­tion had already been es­tab­lished or is a new one. One in­ter­est­ing con­se­quence is that the ap­plic­a­tion re­con­nect­ing to a sub­scrip­tion may not be the same ap­plic­a­tion that dis­con­nec­ted earlier. As long as the new ap­plic­a­tion uses the same topic, the same con­nec­tion fact­ory (and so the same client ID), and the same sub­scrip­tion name as the old ap­plic­a­tion, the mes­saging system cannot dis­tin­guish between the two ap­plic­a­tions and will de­liver all mes­sages to the new ap­plic­a­tion that weren't de­livered to the old ap­plic­a­tion before it dis­con­nec­ted.

                                                                                                                                                                                                                    一旦应用程序对某个主题进行了持久订阅,它将有机会接收发布到该主题的所有消息,即使订阅者关闭了其连接(或者如果应用程序崩溃并且消息传递系统关闭了订阅者的连接)。要阻止消息传递系统为该非活动订阅者排队消息,应用程序必须显式取消订阅其持久订阅。

                                                                                                                                                                                                                    Once an ap­plic­a­tion has a dur­able sub­scrip­tion on a topic, it will have the op­por­tun­ity to re­ceive all mes­sages pub­lished to that topic, even if the sub­scriber closes its con­nec­tion (or if it crashes and the mes­saging system closes the sub­scriber's con­nec­tion for it). To stop the mes­saging system from queuing mes­sages for this in­act­ive sub­scriber, the ap­plic­a­tion must ex­pli­citly un­sub­scribe its dur­able sub­scrip­tion.

                                                                                                                                                                                                                    订阅者.close();
                                                                                                                                                                                                                    // 订阅者现在处于非活动状态,消息将被保存
                                                                                                                                                                                                                    session.unsubscribe(订阅名称);
                                                                                                                                                                                                                    // 订阅被删除
                                                                                                                                                                                                                    
                                                                                                                                                                                                                    sub­scriber.close();
                                                                                                                                                                                                                    // sub­scriber is now in­act­ive, mes­sages will be saved
                                                                                                                                                                                                                    ses­sion.un­sub­scribe(sub­scrip­tion­Name);
                                                                                                                                                                                                                    // sub­scrip­tion is re­moved
                                                                                                                                                                                                                    

                                                                                                                                                                                                                    一旦订阅者取消订阅,该订阅就会从主题中删除,并且消息将不再传递给该订阅者。

                                                                                                                                                                                                                    Once the sub­scriber is un­sub­scribed, the sub­scrip­tion is re­moved from the topic, and mes­sages will no longer be de­livered to this sub­scriber.



                                                                                                                                                                                                                      幂等接收器

                                                                                                                                                                                                                      Idempotent Receiver

                                                                                                                                                                                                                      即使发送方应用程序仅发送一次消息,接收方应用程序也可能多次接收该消息。

                                                                                                                                                                                                                      Even when a sender ap­plic­a­tion sends a mes­sage only once, the re­ceiver ap­plic­a­tion may re­ceive the mes­sage more than once.

                                                                                                                                                                                                                      消息接收者如何处理重复的消息?

                                                                                                                                                                                                                      How can a mes­sage re­ceiver deal with du­plic­ate mes­sages?



                                                                                                                                                                                                                      第 3 章“消息传递系统”中的通道模式讨论了如何通过使用保证传递来使消息传递通道可靠。然而,即使是一些可靠的消息传递实现也可能会产生重复的消息。在其他场景下,保证交付可能不可用,因为通信依赖于本质上不可靠的协议。许多 B2B(企业对企业)集成场景就是这种情况,其中消息必须使用 HTTP 通过 Internet 发送。在这些情况下,通常只能通过重新发送消息直到接收者返回确认来保证消息传递。但是,如果由于连接不可靠而导致确认丢失,发送方可能会重新发送接收方已收到的消息(见图)。

                                                                                                                                                                                                                      The chan­nel pat­terns in Chapter 3, "Mes­saging Sys­tems," dis­cuss how to make mes­saging chan­nels re­li­able by using Guar­an­teed De­liv­ery. How­ever, even some re­li­able mes­saging im­ple­ment­a­tions can pro­duce du­plic­ate mes­sages. In other scen­arios, Guar­an­teed De­liv­ery may not be avail­able be­cause the com­mu­nic­a­tion relies on in­her­ently un­re­li­able pro­to­cols. This is the case in many B2B (busi­ness-to-busi­ness) in­teg­ra­tion scen­arios where mes­sages have to be sent over the In­ter­net using HTTP. In these cases, mes­sage de­liv­ery can gen­er­ally only be guar­an­teed by re­send­ing the mes­sage until an ac­know­ledg­ment is re­turned from the re­cip­i­ent. How­ever, if the ac­know­ledg­ment is lost due to an un­re­li­able con­nec­tion, the sender may resend a mes­sage that the re­ceiver had already re­ceived (see figure).

                                                                                                                                                                                                                      由于发送确认时出现问题而导致消息重复

                                                                                                                                                                                                                      Mes­sage Du­plic­a­tion Be­cause of Prob­lem Send­ing Ac­know­ledg­ment

                                                                                                                                                                                                                      图形/10inf24.gif

                                                                                                                                                                                                                      许多消息传递系统都采用内置机制来消除重复消息,以便应用程序不必担心重复消息。然而,消除消息传递基础设施内的重复项会导致额外的开销。例如,如果接收器本质上对重复消息具有弹性,则在允许重复的情况下,可以增加处理查询式命令消息的消息传递吞吐量的无状态接收器。因此,某些消息传递系统仅提供至少一次传递,并让应用程序处理重复的消息。其他允许应用程序指定是否处理重复项(例如,JMS 规范定义了DUPS_OK_ACKNOWLEDGE模式)。

                                                                                                                                                                                                                      Many mes­saging sys­tems in­cor­por­ate built-in mech­an­isms to elim­in­ate du­plic­ate mes­sages so that the ap­plic­a­tion does not have to worry about du­plic­ates. How­ever, elim­in­at­ing du­plic­ates inside the mes­saging in­fra­struc­ture causes ad­di­tional over­head. If the re­ceiver is in­her­ently re­si­li­ent against du­plic­ate mes­sages­for ex­ample, a state­less re­ceiver that pro­cesses query-style Com­mand Mes­sagesmes­saging through­put can be in­creased if du­plic­ates are al­lowed. For this reason, some mes­saging sys­tems only provide at-least-once de­liv­ery and let the ap­plic­a­tion deal with du­plic­ate mes­sages. Others allow the ap­plic­a­tion to spe­cify whether or not it deals with du­plic­ates (for ex­ample, the JMS spe­cific­a­tion defines a DUPS_OK_AC­KNOW­LEDGE mode).

                                                                                                                                                                                                                      另一种可能产生重复消息的场景是失败的分布式事务。许多通过商业适配器连接到消息传递基础设施的打包应用程序无法正确参与分布式两阶段提交。当一条消息发送到多个应用程序并且该消息导致这些应用程序中的一个或多个失败时,可能很难从这种不一致状态中恢复。如果接收方被设计为忽略重复消息,则发送方只需将消息重新发送给所有接收方即可。那些已经接收并处理原始消息的收件人将简单地忽略重新发送的消息。那些无法正确使用原始消息的应用程序将应用重新发送的消息。

                                                                                                                                                                                                                      An­other scen­ario that can pro­duce du­plic­ate mes­sages is a failed dis­trib­uted trans­ac­tion. Many pack­aged ap­plic­a­tions that are con­nec­ted to the mes­saging in­fra­struc­ture through com­mer­cial ad­apters cannot prop­erly par­ti­cip­ate in a dis­trib­uted two-phase commit. When a mes­sage is sent to mul­tiple ap­plic­a­tions and the mes­sage causes one or more of these ap­plic­a­tions to fail, it may be dif­fi­cult to re­cover from this in­con­sist­ent state. If re­ceiv­ers are de­signed to ignore du­plic­ate mes­sages, the sender can simply resend the mes­sage to all re­cip­i­ents. Those re­cip­i­ents that had already re­ceived and pro­cessed the ori­ginal mes­sage will simply ignore the resend. Those ap­plic­a­tions that were not able to prop­erly con­sume the ori­ginal mes­sage will apply the mes­sage that was resent.

                                                                                                                                                                                                                      将接收器设计为幂等接收器,即可以安全地多次接收同一消息的接收器。

                                                                                                                                                                                                                      Design a re­ceiver to be an Idem­potent Re­ceiver, one that can safely re­ceive the same mes­sage mul­tiple times.



                                                                                                                                                                                                                      幂等一词在数学中用于描述一个函数,如果将该函数应用于自身,则产生相同的结果:f ( x ) = f ( f ( x ) ) 。在消息传递中,这个概念转化为一条消息,无论接收一次还是多次,都具有相同的效果。这意味着即使接收者收到同一消息的重复项,也可以安全地重新发送消息,而不会引起任何问题。

                                                                                                                                                                                                                      The term idem­potent is used in math­em­at­ics to de­scribe a func­tion that pro­duces the same result if it is ap­plied to itself: f (x) = f (f (x)). In Mes­saging this con­cepts trans­lates into a mes­sage that has the same effect whether it is re­ceived once or mul­tiple times. This means that a mes­sage can safely be resent without caus­ing any prob­lems even if the re­ceiver re­ceives du­plic­ates of the same mes­sage.

                                                                                                                                                                                                                      幂等性可以通过两种主要方式实现:

                                                                                                                                                                                                                      Idem­po­tency can be achieved through two primary means:

                                                                                                                                                                                                                      1. 显式重复数据删除,即删除重复消息。

                                                                                                                                                                                                                      2. Ex­pli­cit de-duping, which is the re­moval of du­plic­ate mes­sages.

                                                                                                                                                                                                                      3. 定义消息语义以支持幂等性。

                                                                                                                                                                                                                      4. De­fin­ing the mes­sage se­mantics to sup­port idem­po­tency.

                                                                                                                                                                                                                      收件人可以通过跟踪已收到的消息来显式删除重复消息(假设 de-dupe 是一个正确的英语单词)。唯一的消息标识符简化了此任务,并有助于检测具有相同消息内容的两条合法消息到达的情况。通过使用单独的字段(消息标识符),我们不会将重复消息的语义与消息内容联系起来。然后,我们为每条消息分配一个唯一的消息标识符。许多消息传递系统(例如符合 JMS 的消息传递工具)会自动为每条消息分配唯一的消息标识符,而应用程序无需担心它们。

                                                                                                                                                                                                                      The re­cip­i­ent can ex­pli­citly de-dupe mes­sages (let's assume de-dupe is a proper Eng­lish word) by keep­ing track of mes­sages that it already re­ceived. A unique mes­sage iden­ti­fier sim­pli­fies this task and helps detect those cases where two le­git­im­ate mes­sages with the same mes­sage con­tent arrive. By using a sep­ar­ate field, the mes­sage iden­ti­fier, we do not tie the se­mantics of a du­plic­ate mes­sage to the mes­sage con­tent. We then assign a unique mes­sage iden­ti­fier to each mes­sage. Many mes­saging sys­tems, such as JMS-com­pli­ant mes­saging tools, auto­mat­ic­ally assign unique mes­sage iden­ti­fi­ers to each mes­sage without the ap­plic­a­tions having to worry about them.

                                                                                                                                                                                                                      为了基于消息标识符来检测和消除重复消息,消息接收者必须保留已接收到的消息标识符的列表。关键的设计决策之一是保留消息历史记录多长时间以及是否将历史记录保留到永久存储(例如磁盘)。该决定主要取决于发送者和接收者之间的合同。在最简单的情况下,发送者一次发送一条消息,在每条消息后等待接收者的确认。在这种情况下,接收方将任何传入消息的消息标识符与先前消息的标识符进行比较就足够了。如果标识符相同,它将忽略新消息。有效地,接收者保留单个消息的历史记录。在实践中,这种通信方式可能非常低效,特别是当延迟(消息从发送方传输到接收方的时间)相对于所需的消息吞吐量而言非常显着时。在这些情况下,发送者可能想要发送一整组消息而不等待每个消息的确认。但这意味着接收方必须为已接收的消息保留更长的标识符历史记录。接收方“内存”的大小取决于发送方在没有得到接收方确认的情况下可以发送的消息数量。这个问题类似于 这种通信方式的效率可能非常低,尤其是当延迟(消息从发送方传输到接收方的时间)相对于所需的消息吞吐量而言相当大时。在这些情况下,发送者可能想要发送一整组消息而不等待每个消息的确认。但这意味着接收方必须为已接收的消息保留更长的标识符历史记录。接收方“内存”的大小取决于发送方在没有得到接收方确认的情况下可以发送的消息数量。这个问题类似于 这种通信方式的效率可能非常低,特别是当延迟(消息从发送方传输到接收方的时间)相对于所需的消息吞吐量而言相当大时。在这些情况下,发送者可能想要发送一整组消息而不等待每个消息的确认。但这意味着接收方必须为已接收的消息保留更长的标识符历史记录。接收方“内存”的大小取决于发送方在没有得到接收方确认的情况下可以发送的消息数量。这个问题类似于 特别是如果延迟(消息从发送方传输到接收方的时间)相对于所需的消息吞吐量而言很重要。在这些情况下,发送者可能想要发送一整组消息而不等待每个消息的确认。但这意味着接收方必须为已接收的消息保留更长的标识符历史记录。接收方“内存”的大小取决于发送方在没有得到接收方确认的情况下可以发送的消息数量。这个问题类似于 特别是如果延迟(消息从发送方传输到接收方的时间)相对于所需的消息吞吐量而言很重要。在这些情况下,发送者可能想要发送一整组消息而不等待每个消息的确认。但这意味着接收方必须为已接收的消息保留更长的标识符历史记录。接收方“内存”的大小取决于发送方在没有得到接收方确认的情况下可以发送的消息数量。这个问题类似于 接收者必须为已接收的消息保留更长的标识符历史记录。接收方“内存”的大小取决于发送方在没有得到接收方确认的情况下可以发送的消息数量。这个问题类似于 接收者必须为已接收的消息保留更长的标识符历史记录。接收方“内存”的大小取决于发送方在没有得到接收方确认的情况下可以发送的消息数量。这个问题类似于重排序器

                                                                                                                                                                                                                      In order to detect and elim­in­ate du­plic­ate mes­sages based on the mes­sage iden­ti­fier, the mes­sage re­cip­i­ent has to keep a list of already re­ceived mes­sage iden­ti­fi­ers. One of the key design de­cisions is how long to keep this his­tory of mes­sages and whether to per­sist the his­tory to per­man­ent stor­age such as disk. This de­cision de­pends primar­ily on the con­tract between the sender and the re­ceiver. In the simplest case, the sender sends one mes­sage at a time, await­ing the re­ceiver's ac­know­ledg­ment after every mes­sage. In this scen­ario, it is suf­fi­cient for the re­ceiver to com­pare the mes­sage iden­ti­fier of any in­com­ing mes­sage to the iden­ti­fier of the pre­vi­ous mes­sage. It will then ignore the new mes­sage if the iden­ti­fi­ers are identical. Ef­fect­ively, the re­ceiver keeps a his­tory of a single mes­sage. In prac­tice, this style of com­mu­nic­a­tion can be very in­ef­fi­cient, es­pe­cially if the latency (the time for the mes­sage to travel from the sender to the re­ceiver) is sig­ni­fic­ant re­l­at­ive to the de­sired mes­sage through­put. In these situ­ations, the sender may want to send a whole set of mes­sages without await­ing ac­know­ledg­ment for each one. This im­plies, though, that the re­ceiver has to keep a longer his­tory of iden­ti­fi­ers for already re­ceived mes­sages. The size of the re­ceiver's "memory" de­pends on the number of mes­sages the sender can send without having gotten an ac­know­ledg­ment from the re­ceiver. This prob­lem re­sembles the con­sid­er­a­tions presen­ted in the Resequen­cer.

                                                                                                                                                                                                                      消除重复消息是另一个例子,通过仔细研究低级 TCP/IP 协议,我们可以学到很多东西。当 IP 网络数据包通过网络路由时,可能会生成重复的数据包。TCP/IP 协议通过为每个数据包附加唯一的标识符来确保消除重复的数据包。发送方和接收方协商接收方分配的“窗口大小”,以便检测重复项。有关 TCP/IP 如何实现此机制的全面讨论,请参阅 [ Stevens ]。

                                                                                                                                                                                                                      Elim­in­at­ing du­plic­ate mes­sages is an­other ex­ample where we can learn quite a bit by having a closer look at the low-level TCP/IP pro­tocol. When IP net­work pack­ets are routed across the net­work, du­plic­ate pack­ets can be gen­er­ated. The TCP/IP pro­tocol en­sures elim­in­a­tion of du­plic­ate pack­ets by at­tach­ing a unique iden­ti­fier to each packet. Sender and re­ceiver ne­go­ti­ate a "window size" that the re­cip­i­ent al­loc­ates in order to detect du­plic­ates. For a thor­ough dis­cus­sion of how TCP/IP im­ple­ments this mech­an­ism, see [Stevens].

                                                                                                                                                                                                                      在某些情况下,使用业务密钥作为消息标识符并让持久层处理重复数据删除可能很诱人。例如,假设应用程序将传入订单保存到数据库中。如果每个订单都包含唯一的订单号,并且我们将数据库配置为在订单号字段上使用唯一键,则如果收到重复的订单消息,则插入数据库的操作将失败。这个解决方案看起来很优雅,因为我们将重复检查委托给数据库系统,这在检测重复键方面非常有效。但我们必须谨慎,因为我们将双重语义与单个字段相关联。具体来说,我们将与基础设施相关的语义(重复消息)绑定到业务字段(订单号)。想象一下,业务需求发生变化,以便客户可以通过发送具有相同订单号的另一条消息来修改现有订单(这很常见)。我们现在必须更改消息结构,因为我们将唯一消息标识符绑定到业务字段。因此,最好避免使用双重语义重载单个字段。

                                                                                                                                                                                                                      In some cases, it may be tempt­ing to use a busi­ness key as the mes­sage iden­ti­fier and let the per­sist­ence layer handle the de-duping. For ex­ample, let's assume that an ap­plic­a­tion per­sists in­com­ing orders into a data­base. If each order con­tains a unique order number, and we con­fig­ure the data­base to use a unique key on the order number field, the insert op­er­a­tion into the data­base would fail if a du­plic­ate order mes­sage is re­ceived. This solu­tion ap­pears el­eg­ant be­cause we del­eg­ated the check­ing of du­plic­ates to the data­base sys­tems, which is very ef­fi­cient at de­tect­ing du­plic­ate keys. But we have to be cau­tious be­cause we as­so­ci­ated dual se­mantics to a single field. Spe­cific­ally, we tied in­fra­struc­ture-re­lated se­mantics (a du­plic­ate mes­sage) to a busi­ness field (order number). Ima­gine that the busi­ness re­quire­ments change so that cus­tom­ers can amend ex­ist­ing orders by send­ing an­other mes­sage with the same order number (this is quite common). We would now have to make changes to our mes­sage struc­ture, since we tied the unique mes­sage iden­ti­fier to a busi­ness field. There­fore, it is best to avoid over­load­ing a single field with dual se­mantics.

                                                                                                                                                                                                                      使用数据库强制重复数据删除有时是通过消息基础设施供应商提供的数据库适配器来完成的。在许多情况下,这些适配器无法消除重复项,因此必须将此功能委托给数据库。

                                                                                                                                                                                                                      Using a data­base to force de-duping is some­times done with data­base ad­apters provided by the mes­saging in­fra­struc­ture vendors. In many cases, these ad­apters are not cap­able of elim­in­at­ing du­plic­ates, so this func­tion has to be del­eg­ated to the data­base.

                                                                                                                                                                                                                      实现幂等性的另一种方法是定义消息的语义,以便重新发送消息不会影响系统。例如,我们可以将消息更改为“将帐户 12345 的余额设置为 110 美元”,而不是将消息定义为“向帐户 12345 添加 10 美元”。如果当前帐户余额为 100 美元,这两条消息将获得相同的结果。第二条消息是幂等的,因为接收两次不会有任何效果。诚然,此示例忽略了并发情况,例如另一条消息“将帐户 12345 的余额设置为 150 美元”在原始消息和重复消息之间到达的情况。

                                                                                                                                                                                                                      An al­tern­at­ive ap­proach to achieve idem­po­tency is to define the se­mantics of a mes­sage such that re­send­ing the mes­sage does not impact the system. For ex­ample, rather than de­fin­ing a mes­sage as "Add $10 to ac­count 12345," we could change the mes­sage to "Set the bal­ance of ac­count 12345 to $110." Both mes­sages achieve the same result if the cur­rent ac­count bal­ance is $100. The second mes­sage is idem­potent be­cause re­ceiv­ing it twice will not have any effect. Ad­mit­tedly, this ex­ample ig­nores con­cur­rency situ­ationsfor ex­ample, the case where an­other mes­sage, "Set the bal­ance of ac­count 12345 to $150," ar­rives between the ori­ginal and the du­plic­ate mes­sage.

                                                                                                                                                                                                                      示例: 微软 IDL (MIDL)

                                                                                                                                                                                                                      Ex­ample: Mi­crosoft IDL (MIDL)

                                                                                                                                                                                                                      Microsoft 接口定义语言 (MIDL) 支持幂等性概念作为远程调用语义的一部分。可以使用[idempot]属性将远程过程声明为幂等。MIDL 规范指出“ [幂等]属性指定操作不会修改状态信息并在每次执行时返回相同的结果。多次执行例程与执行一次具有相同的效果。”

                                                                                                                                                                                                                      The Mi­crosoft In­ter­face Defin­i­tion Lan­guage (MIDL) sup­ports the concept of idem­po­tency as part of the remote call se­mantics. A remote pro­ced­ure can be de­clared as idem­potent by using the [idem­potent] at­trib­ute. The MIDL spe­cific­a­tion states that the "[idem­potent] at­trib­ute spe­cifies that an op­er­a­tion does not modify state in­form­a­tion and re­turns the same res­ults each time it is per­formed. Per­form­ing the routine more than once has the same effect as per­form­ing it once."

                                                                                                                                                                                                                      接口IFoo;
                                                                                                                                                                                                                      [
                                                                                                                                                                                                                          uuid(5767B67C-3F02-40ba-8B85-D8516F20A83B),
                                                                                                                                                                                                                          指针默认值(唯一)
                                                                                                                                                                                                                      ]
                                                                                                                                                                                                                      
                                                                                                                                                                                                                      接口IFoo
                                                                                                                                                                                                                      {
                                                                                                                                                                                                                          [幂等]
                                                                                                                                                                                                                          bool 获取客户名称
                                                                                                                                                                                                                          (
                                                                                                                                                                                                                              [in] int 客户ID,
                                                                                                                                                                                                                              [输出] 字符*名称
                                                                                                                                                                                                                          );
                                                                                                                                                                                                                      }
                                                                                                                                                                                                                      
                                                                                                                                                                                                                      in­ter­face IFoo;
                                                                                                                                                                                                                      [
                                                                                                                                                                                                                          uuid(5767B67C-3F02-40ba-8B85-D8516F20A83B),
                                                                                                                                                                                                                          point­er­_de­fault(unique)
                                                                                                                                                                                                                      ]
                                                                                                                                                                                                                      
                                                                                                                                                                                                                      in­ter­face IFoo
                                                                                                                                                                                                                      {
                                                                                                                                                                                                                          [idem­potent]
                                                                                                                                                                                                                          bool Get­Cus­tomer­Name
                                                                                                                                                                                                                          (
                                                                                                                                                                                                                              [in] int Cus­tom­erID,
                                                                                                                                                                                                                              [out] char *Name
                                                                                                                                                                                                                          );
                                                                                                                                                                                                                      }
                                                                                                                                                                                                                      



                                                                                                                                                                                                                        服务激活器

                                                                                                                                                                                                                        Service Activator

                                                                                                                                                                                                                        图形/serviceactivator_icon.gif

                                                                                                                                                                                                                        一个应用程序有一项服务希望可供其他应用程序使用。

                                                                                                                                                                                                                        An ap­plic­a­tion has a ser­vice that it would like to make avail­able to other ap­plic­a­tions.

                                                                                                                                                                                                                        应用程序如何设计可通过各种消息传递技术和非消息传递技术调用的服务?

                                                                                                                                                                                                                        How can an ap­plic­a­tion design a ser­vice to be in­voked both via vari­ous mes­saging tech­no­lo­gies and via non-mes­saging tech­niques?



                                                                                                                                                                                                                        应用程序可能不想选择服务(服务层[ EAA ]中的操作)是否可以同步或异步调用:它可能希望支持同一服务的两种方法。然而,技术似乎可以迫使人们做出选择。例如,使用 EJB 实现的应用程序可能需要使用会话 bean 来支持同步客户端,但需要使用 MDB 来支持消息传递客户端。[1]

                                                                                                                                                                                                                        An ap­plic­a­tion may not want to choose whether a ser­vice (an op­er­a­tion in a Ser­vice Layer [EAA]) can be in­voked syn­chron­ously or asyn­chron­ously: It may want to sup­port both ap­proaches for the same ser­vice. Yet, tech­no­lo­gies can seem to force the choice. For ex­ample, an ap­plic­a­tion im­ple­men­ted using EJB may need to use a ses­sion bean to sup­port syn­chron­ous cli­ents but an MDB to sup­port mes­saging cli­ents.[1]

                                                                                                                                                                                                                        [1]感谢 Mark Weitzel 提供的这个例子。

                                                                                                                                                                                                                        [1] Thanks to Mark Weitzel for this ex­ample.

                                                                                                                                                                                                                        设计与其他应用程序(例如 B2B 应用程序)一起使用的应用程序的开发人员可能不知道他们正在与哪些其他应用程序通信以及各种通信将如何工作。有太多不同的消息传递技术和数据格式,无法尝试支持每一种以防万一。[2]

                                                                                                                                                                                                                        De­ve­lopers design­ing an ap­plic­a­tion to work with other ap­plic­a­tions, such as a B2B ap­plic­a­tion, may not know what other ap­plic­a­tions they're com­mu­nic­at­ing with and how the vari­ous com­mu­nic­a­tions will work. There are too many dif­fer­ent mes­saging tech­no­lo­gies and data formats to try to sup­port every one just in case it's needed.[2]

                                                                                                                                                                                                                        [2]感谢 Luke Hohmann 提供此示例。

                                                                                                                                                                                                                        [2] Thanks to Luke Hohmann for this ex­ample.

                                                                                                                                                                                                                        接收和处理消息涉及多个步骤;分开这些步骤可能很困难并且不必要地复杂。然而,将接收消息、提取消息内容以及作用于这些内容来执行工作的这些任务混合在一起的消息端点代码可能难以重用。

                                                                                                                                                                                                                        Re­ceiv­ing and pro­cess­ing a mes­sage in­volves a number of steps; sep­ar­at­ing these steps can be dif­fi­cult and un­ne­ces­sar­ily com­plex. Yet, Mes­sage En­d­point code that mixes to­gether these tasks­re­ceiv­ing the mes­sage, ex­tract­ing its con­tents, and acting on those con­tents to per­form work­can be dif­fi­cult to reuse.

                                                                                                                                                                                                                        当为多种通信风格设计客户端时,似乎有必要为每种风格重新实现服务。这使得支持每种新样式变得很麻烦,并产生每种样式可能不会产生完全相同的行为的风险。我们需要的是一种单一服务支持多种通信方式的方法。

                                                                                                                                                                                                                        When design­ing cli­ents for mul­tiple styles of com­mu­nic­a­tion, it may well seem ne­ces­sary to re­im­ple­ment the ser­vice for each style. This makes sup­port­ing each new style cum­ber­some and cre­ates the risk that each style may not pro­duce quite the same be­ha­vior. What is needed is a way for a single ser­vice to sup­port mul­tiple styles of com­mu­nic­a­tion.

                                                                                                                                                                                                                        设计一个服务激活器,将通道上的消息连接到正在访问的服务。

                                                                                                                                                                                                                        Design a Ser­vice Ac­tiv­ator that con­nects the mes­sages on the chan­nel to the ser­vice being ac­cessed.

                                                                                                                                                                                                                        图形/10inf25.gif



                                                                                                                                                                                                                        服务激活器可以是单向的(仅请求)或双向的(请求-回复) 。服务可以像方法调用一样简单,同步且非远程,或者是服务层[ EAA ] 的一部分。激活器可以被硬编码为始终调用相同的服务,或者它可以使用反射来调用消息指示的服务。激活器处理所有消息传递详细信息并像任何其他客户端一样调用服务,这样服务甚至不知道它是通过消息传递调用的。

                                                                                                                                                                                                                        A Ser­vice Ac­tiv­ator can be one-way (re­quest only) or two-way (Re­quest-Reply ). The ser­vice can be as simple as a method call­syn­chron­ous and non-re­mote­per­haps part of a Ser­vice Layer [EAA]. The ac­tiv­ator can be hard-coded to always invoke the same ser­vice, or it can use re­flec­tion to invoke the ser­vice in­dic­ated by the mes­sage. The ac­tiv­ator handles all of the mes­saging de­tails and in­vokes the ser­vice like any other client, such that the ser­vice doesn't even know it's being in­voked through mes­saging.

                                                                                                                                                                                                                        请求-回复的服务激活器序列

                                                                                                                                                                                                                        Ser­vice Ac­tiv­ator Se­quence for Re­quest-Reply

                                                                                                                                                                                                                        图形/10inf26.gif

                                                                                                                                                                                                                        服务激活器处理接收请求消息(作为轮询消费者或事件驱动消费者) 。它知道消息的格式并处理消息以提取所需的信息,以了解要调用什么服务以及要传入什么参数值。然后,激活器像该服务的任何其他客户端一样调用该服务,并在服务执行时阻塞。当服务完成并返回一个值时,激活器可以选择创建一条包含该值的回复消息并将其返回给请求者。(回复使服务调用成为请求-回复消息传递的示例。)

                                                                                                                                                                                                                        The Ser­vice Ac­tiv­ator handles re­ceiv­ing the re­quest mes­sage (either as a Polling Con­sumer or as an Event-Driven Con­sumer ). It knows the mes­sage's format and pro­cesses the mes­sage to ex­tract the in­form­a­tion ne­ces­sary to know what ser­vice to invoke and what para­meter values to pass in. The ac­tiv­ator then in­vokes the ser­vice just like any other client of the ser­vice and blocks while the ser­vice ex­ecutes. When the ser­vice com­pletes and re­turns a value, the ac­tiv­ator can op­tion­ally create a reply mes­sage con­tain­ing the value and return it to the re­questor. (The reply makes the ser­vice in­voc­a­tion an ex­ample of Re­quest-Reply mes­saging.)

                                                                                                                                                                                                                        服务激活器使服务能够被编写,就好像它总是被同步调用一样。激活器接收异步消息,确定要调用什么服务以及传递什么数据,然后同步调用该服务。该服务设计为无需消息传递即可工作,但激活器使其可以通过消息传递轻松调用。

                                                                                                                                                                                                                        A Ser­vice Ac­tiv­ator en­ables a ser­vice to be writ­ten as though it's always going to be in­voked syn­chron­ously. The ac­tiv­ator re­ceives the asyn­chron­ous mes­sage, de­term­ines what ser­vice to invoke and what data to pass it, and then in­vokes the ser­vice syn­chron­ously. The ser­vice is de­signed to work without mes­saging, yet the ac­tiv­ator en­ables it to easily be in­voked via mes­saging.

                                                                                                                                                                                                                        如果服务激活器无法成功处理该消息,则该消息无效,应将其移至无效消息通道。如果可以处理消息并且成功调用服务,则执行服务过程中发生的任何错误都是应用程序中的语义错误,应由应用程序处理。

                                                                                                                                                                                                                        If the Ser­vice Ac­tiv­ator cannot pro­cess the mes­sage suc­cess­fully, the mes­sage is in­valid and should be moved to an In­valid Mes­sage Chan­nel. If the mes­sage can be pro­cessed and the ser­vice is in­voked suc­cess­fully, then any errors that occur as part of ex­ecut­ing the ser­vice are se­mantic errors in the ap­plic­a­tion and should be handled by the ap­plic­a­tion.

                                                                                                                                                                                                                        开发人员可能仍然无法预测合作伙伴可能希望访问其服务的每种方式,但他们至少知道他们的应用程序将提供哪些服务并且可以实现这些服务。然后,根据需要为不同的技术和格式实施新的激活器相对容易。

                                                                                                                                                                                                                        De­ve­lopers still may not be able to pre­dict every way part­ners might wish to access their ser­vices, but they do at least know what ser­vices their ap­plic­a­tion will provide and can im­ple­ment those. Then, im­ple­ment­ing new ac­tiv­at­ors for dif­fer­ent tech­no­lo­gies and formats as needed is re­l­at­ively easy.

                                                                                                                                                                                                                        服务激活器模式也记录在 [ CoreJ2EE] 中,这是该模式最初命名的地方。该版本的模式与此版本有些不同,它假设激活器是事件驱动的消费者,并且服务已经存在,因此可以将激活器添加到服务中,但是两个版本都以非常相似的方式针对同一问题提出了相同的解决方案时尚。服务激活器与半同步/半异步模式[ POSA2]相关,该模式将服务处理分为同步层和异步层。

                                                                                                                                                                                                                        This Ser­vice Ac­tiv­ator pat­tern is also doc­u­mented in [Core­J2EE], which is where the pat­tern was ori­gin­ally named. That ver­sion of the pat­tern is some­what dif­fer­ent from this oneit as­sumes the ac­tiv­ator is an Event-Driven Con­sumer and that the ser­vice already exists so that the ac­tiv­ator can be added to the ser­vice­but both ver­sions pro­pose the same solu­tion to the same prob­lem in a very sim­ilar fash­ion. Ser­vice Ac­tiv­ator is re­lated to the Half-Sync/Half-Async pat­tern [POSA2], which sep­ar­ates ser­vice pro­cess­ing into syn­chron­ous and asyn­chron­ous layers.

                                                                                                                                                                                                                        服务激活器通常接收命令消息,其中描述了要调用的服务。服务激活器充当消息传递网关,将消息传递详细信息与服务分开。激活器可以是轮询消费者或事件驱动消费者。如果服务是事务性的,则激活器应该是事务性客户端,以便消息消费可以参与与服务调用相同的事务。多个激活器可以是竞争消费者或消息。如果一个Service Activator无法成功处理消息,它应该将消息发送到Invalid Message Channel

                                                                                                                                                                                                                        A Ser­vice Ac­tiv­ator usu­ally re­ceives Com­mand Mes­sages, which de­scribe what ser­vice to invoke. A Ser­vice Ac­tiv­ator serves as a Mes­saging Gate­way, sep­ar­at­ing the mes­saging de­tails from the ser­vice. The ac­tiv­ator can be a Polling Con­sumer or an Event-Driven Con­sumer. If the ser­vice is trans­ac­tional, the ac­tiv­ator should be a Trans­ac­tional Client so that the mes­sage con­sump­tion can par­ti­cip­ate in the same trans­ac­tion as the ser­vice in­voc­a­tion. Mul­tiple ac­tiv­at­ors can be Com­pet­ing Con­sumers or co­ordin­ated by a Mes­sage Dis­patcher. If a Ser­vice Ac­tiv­ator cannot pro­cess a mes­sage suc­cess­fully, it should send the mes­sage to an In­valid Mes­sage Chan­nel.

                                                                                                                                                                                                                        示例: J2EE Enterprise JavaBean

                                                                                                                                                                                                                        Ex­ample: J2EE En­ter­prise Java­Beans

                                                                                                                                                                                                                        例如,考虑 J2EE 中的 EJB [ EJB 2.0 ]:将服务封装为会话 Bean,然后为各种消息传递场景实现 MDB:一个用于使用一种格式的消息的 JMS 目标;另一个用于使用一种格式的消息的 MDB。另一个使用另一种格式用于不同的目的地;另一个用于 Web 服务/SOAP 消息;等等。每个通过调用服务来处理消息的 MDB 都是一个Service Activator 。希望同步调用服务的客户端可以直接访问会话 bean。

                                                                                                                                                                                                                        Con­sider, for ex­ample, EJBs [EJB 2.0] in J2EE: En­cap­su­late the ser­vice as a ses­sion bean, and then im­ple­ment MDBs for vari­ous mes­saging scen­arios: one for a JMS des­tin­a­tion using mes­sages of one format; an­other for a dif­fer­ent des­tin­a­tion using an­other format; an­other for a Web ser­vice/SOAP mes­sage; and so on. Each MDB that pro­cesses the mes­sage by in­vok­ing the ser­vice is a Ser­vice Ac­tiv­ator. Cli­ents that wish to invoke the ser­vice syn­chron­ously can access the ses­sion bean dir­ectly.



                                                                                                                                                                                                                          介绍

                                                                                                                                                                                                                          Introduction

                                                                                                                                                                                                                          虽然开发消息传递解决方案并非易事,但在生产中操作此类解决方案同样具有挑战性:基于消息的集成解决方案可能会在一天内生成、路由和转换数千甚至数百万条消息。我们必须处理参与系统中的异常、性能瓶颈和变化。为了使事情变得更具挑战性,组件分布在许多可以驻留在多个位置的平台和机器上。

                                                                                                                                                                                                                          While de­vel­op­ing a mes­saging solu­tion is no easy task, op­er­at­ing such a solu­tion in pro­duc­tion is equally chal­len­ging: A mes­sage-based in­teg­ra­tion solu­tion may pro­duce, route, and trans­form thou­sands or even mil­lions of mes­sages in a day. We have to deal with ex­cep­tions, per­form­ance bot­tle­necks, and changes in the par­ti­cip­at­ing sys­tems. To make things ever more chal­len­ging, com­pon­ents are dis­trib­uted across many plat­forms and ma­chines that can reside at mul­tiple loc­a­tions.

                                                                                                                                                                                                                          除了集成分布式打包和定制应用程序固有的复杂性和规模之外,松散耦合的架构优势实际上使测试和调试系统变得更加困难。Martin Fowler 将此称为“架构师的梦想,开发人员的噩梦”症状:松散耦合和间接的架构原则减少了系统彼此之间的假设,从而提供了灵活性。然而,测试消息生产者不知道消息消费者是谁的系统可能具有挑战性。再加上消息传递的异步和时间方面,事情会变得更加复杂。例如,消息传递解决方案甚至可能不是为消息生产者设计的以从接收者接收回复消息。同样,消息传递基础设施通常保证消息的传递,但不保证传递时间。这使得开发依赖于消息传递结果的测试用例变得困难。

                                                                                                                                                                                                                          Be­sides the in­her­ent com­plex­it­ies and scale of in­teg­rat­ing dis­trib­uted pack­aged and custom ap­plic­a­tions, the ar­chi­tec­tural be­ne­fits of loose coup­ling ac­tu­ally make test­ing and de­bug­ging a system harder. Martin Fowler refers to this as the "ar­chi­tect's dream, de­ve­loper's night­mare" symp­tom: Ar­chi­tec­tural prin­ciples of loose coup­ling and in­dir­ec­tion reduce the as­sump­tions sys­tems make about each other and there­fore provide flex­ib­il­ity. How­ever, test­ing a system where a mes­sage pro­du­cer is not aware of who the con­sumers of a mes­sage are can be chal­len­ging. Add to that the asyn­chron­ous and tem­poral as­pects of mes­saging and things get even more com­plic­ated. For ex­ample, the mes­saging solu­tion may not even be de­signed for the mes­sage pro­du­cer to re­ceive a reply mes­sage from the re­cip­i­ent(s). Like­wise, the mes­saging in­fra­struc­ture typ­ic­ally guar­an­tees the de­liv­ery of the mes­sage but not the de­liv­ery time. This makes it hard to de­velop test cases that rely on the res­ults of the mes­sage de­liv­ery.

                                                                                                                                                                                                                          在监控消息解决方案时,我们可以在两个不同的抽象级别跟踪消息流。典型的系统管理解决方案监视正在发送的消息数量或处理消息所需的时间。这些监控解决方案不会检查消息数据,除了消息头中的某些字段(例如消息标识符或消息历史记录) 。相比之下,业务活动监控(BAM) 解决方案重点关注消息中包含的有效负载数据,例如过去一小时内所有订单的美元价值。本节中介绍的许多模式都足够通用,可以用于任一目的。然而,由于 BAM 本身是一个全新的领域,并且与数据仓库有许多复杂性(我们根本没有触及过),因此我们决定在系统管理的背景下讨论这些模式。

                                                                                                                                                                                                                          When mon­it­or­ing a mes­sage solu­tion, we can track the flow of mes­sages at two dif­fer­ent levels of ab­strac­tion. A typ­ical system man­age­ment solu­tion mon­it­ors how many mes­sages are being sent or how long it took a mes­sage to be pro­cessed. These mon­it­or­ing solu­tions do not in­spect the mes­sage data except maybe for some fields in the mes­sage header such as the mes­sage iden­ti­fier or the Mes­sage His­tory. In con­trast, busi­ness activ­ity mon­it­or­ing (BAM) solu­tions focus on the pay­load data con­tained in the mes­sage, for ex­ample, the dollar value of all orders placed in the last hour. Many of the pat­terns presen­ted in this sec­tion are gen­eral enough that they can be used for either pur­pose. How­ever, be­cause BAM is a whole new field in itself and shares many com­plex­it­ies with data ware­hous­ing (some­thing we have not touched on at all), we de­cided to dis­cuss the pat­terns in the con­text of system man­age­ment.

                                                                                                                                                                                                                          系统管理模式旨在满足这些需求,并提供工具来保持复杂的基于消息的系统运行。本章中的模式分为三类:监视和控制、观察和分析消息流量以及测试和调试。

                                                                                                                                                                                                                          System man­age­ment pat­terns are de­signed to ad­dress these re­quire­ments and provide the tools to keep a com­plex mes­sage-based system run­ning. The pat­terns in this chapter fall into three cat­egor­ies: mon­it­or­ing and con­trolling, ob­serving and ana­lyz­ing mes­sage traffic, and test­ing and de­bug­ging.

                                                                                                                                                                                                                          监测与控制

                                                                                                                                                                                                                          Mon­it­or­ing and Con­trolling

                                                                                                                                                                                                                          控制总线提供单点控制来管理和监控分布式解决方案。它将多个组件连接到一个中央管理控制台,该控制台可以显示每个组件的状态并监视通过组件的消息流量。控制台还可用于向组件发送控制命令,例如更改消息流。

                                                                                                                                                                                                                          A Con­trol Bus provides a single point of con­trol to manage and mon­itor a dis­trib­uted solu­tion. It con­nects mul­tiple com­pon­ents to a cent­ral man­age­ment con­sole that can dis­play the status of each com­pon­ent and mon­itor mes­sage traffic through the com­pon­ents. The con­sole can also be used to send con­trol com­mands to com­pon­ents, for in­stance, to change the mes­sage flow.

                                                                                                                                                                                                                          我们可能希望通过其他步骤来路由消息,例如验证或日志记录。由于这些步骤会带来性能开销,因此我们可能希望能够通过控制总线打开和关闭它们。Detour给我们这种能力。

                                                                                                                                                                                                                          We may want to route mes­sages through ad­di­tional steps, such as val­id­a­tion or log­ging. Be­cause these steps can in­tro­duce per­form­ance over­heads, we may want to be able to switch them on and off via the con­trol bus. A Detour gives us this abil­ity.

                                                                                                                                                                                                                          观察和分析消息流量

                                                                                                                                                                                                                          Ob­serving and Ana­lyz­ing Mes­sage Traffic

                                                                                                                                                                                                                          有时,我们希望在不影响主消息流的情况下检查消息的内容。有线窃听使我们能够窃听消息流量。

                                                                                                                                                                                                                          Some­times, we want to in­spect the con­tents of a mes­sage without af­fect­ing the primary mes­sage flow. A Wire Tap allows us to tap into mes­sage traffic.

                                                                                                                                                                                                                          当我们调试基于消息的系统时,了解特定消息的位置非常有帮助。消息历史记录保留消息访问过的所有组件的日志,而不会引入组件之间的依赖关系。

                                                                                                                                                                                                                          When we debug a mes­sage-based system, it is a great aid to know where a spe­cific mes­sage has been. The Mes­sage His­tory keeps a log of all com­pon­ents the mes­sage has vis­ited without in­tro­du­cing de­pend­en­cies between com­pon­ents.

                                                                                                                                                                                                                          虽然消息历史记录与单个消息相关联,但中央消息存储可以提供通过系统的每条消息的完整帐户。与消息历史记录相结合,消息存储可以分析消息可以通过系统的所有可能路径。

                                                                                                                                                                                                                          While the Mes­sage His­tory is tied to an in­di­vidual mes­sage, a cent­ral Mes­sage Store can provide a com­plete ac­count of every mes­sage that traveled through the system. Com­bined with the Mes­sage His­tory, the Mes­sage Store can ana­lyze all pos­sible paths mes­sages can take through the system.

                                                                                                                                                                                                                          Wire TapMessage HistoryMessage Store帮助我们分析消息的异步流。为了跟踪发送到请求-答复服务的消息,我们需要在消息流中插入智能代理。

                                                                                                                                                                                                                          The Wire Tap, Mes­sage His­tory, and Mes­sage Store help us ana­lyze the asyn­chron­ous flow of a mes­sage. In order to track mes­sages sent to re­quest-reply ser­vices, we need to insert a Smart Proxy into the mes­sage stream.

                                                                                                                                                                                                                          测试与调试

                                                                                                                                                                                                                          Test­ing and De­bug­ging

                                                                                                                                                                                                                          在将消息系统部署到生产环境之前对其进行测试是一个非常好的主意。但测试不应该就此停止。您应该积极验证正在运行的消息系统是否继续正常运行。您可以通过定期向系统注入测试消息并验证结果来做到这一点。

                                                                                                                                                                                                                          Test­ing a mes­saging system before de­ploy­ing it into pro­duc­tion is a very good idea. But test­ing should not stop there. You should be act­ively veri­fy­ing that the run­ning mes­saging system con­tin­ues to func­tion prop­erly. You can do this by peri­od­ic­ally in­ject­ing a Test Mes­sage into the system and veri­fy­ing the res­ults.

                                                                                                                                                                                                                          当组件出现故障或行为异常时,很容易在通道上出现不需要的消息。在测试过程中,从通道中删除所有剩余消息非常有用,这样测试中的组件就不会收到“剩余”消息。Channel Purger可以帮我们做到这一点。

                                                                                                                                                                                                                          When a com­pon­ent fails or mis­be­haves, it is easy to end up with un­wanted mes­sages on a chan­nel. During test­ing it is very useful to remove all re­main­ing mes­sages from a chan­nel so that the com­pon­ents under test do not re­ceive "leftover" mes­sages. A Chan­nel Purger does that for us.

                                                                                                                                                                                                                            控制总线

                                                                                                                                                                                                                            Control Bus

                                                                                                                                                                                                                            图形/controlbus_icon.gif

                                                                                                                                                                                                                            当然,企业集成系统是分布式的。事实上,企业消息传递系统的定义品质之一是支持不同系统之间的通信。消息传递系统允许路由和转换信息,以便可以在这些系统之间交换数据。在大多数情况下,这些应用程序分布在多个网络、建筑物、城市或大陆。

                                                                                                                                                                                                                            Nat­ur­ally, en­ter­prise in­teg­ra­tion sys­tems are dis­trib­uted. In fact, one of the de­fin­ing qual­it­ies of an en­ter­prise mes­saging system is to enable com­mu­nic­a­tion between dis­par­ate sys­tems. Mes­saging sys­tems allow in­form­a­tion to be routed and trans­formed so that data can be ex­changed between these sys­tems. In most cases, these ap­plic­a­tions are spread across mul­tiple net­works, build­ings, cities, or con­tin­ents.

                                                                                                                                                                                                                            我们如何有效地管理分布在多个平台和广泛地理区域的消息系统?

                                                                                                                                                                                                                            How can we ef­fect­ively ad­min­is­ter a mes­saging system that is dis­trib­uted across mul­tiple plat­forms and a wide geo­graphic area?



                                                                                                                                                                                                                            分布式、松散耦合的架构具有灵活性和可扩展性。同时,也给该系统的管理和控制带来了严峻的挑战。例如,如何判断所有组件是否都已启动并正在运行?简单的进程状态是不够的,因为进程分布在许多机器上。另外,如果无法从远程计算机获取状态,是否意味着远程计算机无法正常工作,或者与远程计算机的通信可能受到干扰?

                                                                                                                                                                                                                            A dis­trib­uted, loosely coupled ar­chi­tec­ture allows for flex­ib­il­ity and scalab­il­ity. At the same time, it poses ser­i­ous chal­lenges for ad­min­is­tra­tion and con­trol of such a system. For ex­ample, how can you tell whether all com­pon­ents are up and run­ning? A simple pro­cess status won't suf­fice, be­cause pro­cesses are dis­trib­uted across many ma­chines. Also, if you cannot obtain status from a remote ma­chine, does it mean that the remote ma­chine is not func­tion­ing, or might the com­mu­nic­a­tion with the remote ma­chine be dis­turbed?

                                                                                                                                                                                                                            除了了解系统或组件是否启动并运行之外,您还需要监视系统的动态行为。消息吞吐量是多少?是否有任何异常延误?渠道满了吗?其中一些信息需要跟踪组件之间或通过组件的消息传播时间。这需要从不止一台机器收集和组合信息。

                                                                                                                                                                                                                            Be­sides just know­ing whether a system or a com­pon­ent is up and run­ning, you also need to mon­itor the dy­namic be­ha­vior of the system. What is the mes­sage through­put? Are there any un­usual delays? Are chan­nels filling up? Some of this in­form­a­tion re­quires track­ing of mes­sage travel times between com­pon­ents or through com­pon­ents. This re­quires the col­lec­tion and com­bin­a­tion of in­form­a­tion from more than one ma­chine.

                                                                                                                                                                                                                            此外,仅从组件读取信息可能还不够。通常,您需要在系统运行时进行调整或更改配置设置。例如,您可能需要在系统运行时打开或关闭日志记录功能。许多应用程序使用属性文件和错误日志来读取配置信息并报告错误情况。只要应用程序由一台机器或可能由少量机器组成,这种方法往往效果很好。在大型分布式解决方案中,必须使用某种文件传输机制将属性文件复制到远程计算机,这要求每台计算机上的文件系统都可以远程访问。这可能会带来安全风险,并且如果计算机通过可能不支持文件映射协议的互联网或广域网连接,则可能会带来挑战。此外,必须仔细管理本地属性文件的版本,这将是一场管理噩梦。

                                                                                                                                                                                                                            Also, just read­ing in­form­a­tion from com­pon­ents may not be suf­fi­cient. Often, you need to make ad­just­ments or change con­fig­ur­a­tion set­tings while the system is run­ning. For ex­ample, you may need to turn log­ging fea­tures on or off while the system is run­ning. Many ap­plic­a­tions use prop­erty files and error logs to read con­fig­ur­a­tion in­form­a­tion and report error con­di­tions. This ap­proach tends to work well as long as the ap­plic­a­tion con­sists of a single ma­chine or pos­sibly a small number of ma­chines. In a large, dis­trib­uted solu­tion, prop­erty files would have to be copied to remote ma­chines using some file trans­fer mech­an­ism, which re­quires the file system on every ma­chine to be ac­cess­ible re­motely. This can pose se­cur­ity risks and can be chal­len­ging if the ma­chines are con­nec­ted over the In­ter­net or a wide-area net­work that may not sup­port file map­ping pro­to­cols. Also, the ver­sions of the local prop­erty files would have to be man­aged care­ful­lya man­age­ment night­mare wait­ing to happen.

                                                                                                                                                                                                                            尝试利用消息传递基础设施来执行其中一些任务似乎很自然。例如,我们可以向组件发送消息以更改其配置。该控制消息可以像常规消息一样被传输和路由。这将解决大部分沟通问题,但也带来了新的挑战。配置消息应遵守比常规应用程序消息更严格的安全策略。例如,一条格式错误的控制消息很容易导致组件瘫痪。另外,如果由于组件出现故障而导致消息在消息通道上排队怎么办?如果我们发送控制消息来重置组件,该控制消息将与所有其他消息一起排队,并且不会到达遇险的组件。一些消息系统支持消息优先级,这可以帮助将控制消息移动到队列的前面。然而,并非所有系统都提供这种能力,如果队列已满并拒绝接受另一条消息,则优先级可能无济于事。同样,一些控制消息的优先级低于应用消息。如果我们让组件定期发布状态消息,那么延迟或丢失“我还活着”控制消息可能比延迟或丢失“100 万美元的订单”消息要麻烦得多。并非所有系统都提供这种能力,如果队列已满并拒绝接受另一条消息,则优先级可能无济于事。同样,一些控制消息的优先级低于应用消息。如果我们让组件定期发布状态消息,那么延迟或丢失“我还活着”控制消息可能比延迟或丢失“100 万美元的订单”消息要麻烦得多。并非所有系统都提供这种能力,如果队列已满并拒绝接受另一条消息,则优先级可能无济于事。同样,一些控制消息的优先级低于应用消息。如果我们让组件定期发布状态消息,那么延迟或丢失“我还活着”控制消息可能比延迟或丢失“100 万美元的订单”消息要麻烦得多。

                                                                                                                                                                                                                            It seems nat­ural to try to lever­age the mes­saging in­fra­struc­ture to per­form some of these tasks. For ex­ample, we could send a mes­sage to a com­pon­ent to change its con­fig­ur­a­tion. This con­trol mes­sage could be trans­por­ted and routed just like a reg­u­lar mes­sage. This would solve most of the com­mu­nic­a­tion prob­lems but also poses new chal­lenges. Con­fig­ur­a­tion mes­sages should be sub­ject to stricter se­cur­ity policies than are reg­u­lar ap­plic­a­tion mes­sages. For ex­ample, one wrongly format­ted con­trol mes­sage could easily bring a com­pon­ent down. Also, what if mes­sages are queued up on a mes­sage chan­nel be­cause a com­pon­ent is mal­func­tion­ing? If we send a con­trol mes­sage to reset the com­pon­ent, this con­trol mes­sage would get queued up with all the other mes­sages and not reach the com­pon­ent in dis­tress. Some mes­saging sys­tems sup­port mes­sage pri­or­it­ies that can help move con­trol mes­sages to the front of the queue. How­ever, not all sys­tems provide this abil­ity, and the pri­or­ity may not help if a queue is filled to the limit and re­fuses to accept an­other mes­sage. Like­wise, some con­trol mes­sages are of a lower pri­or­ity than ap­plic­a­tion mes­sages. If we have com­pon­ents pub­lish peri­odic status mes­sages, delay­ing or losing an "I am alive" con­trol mes­sage may be a lot less trouble­some than delay­ing or losing the "Order for $1 mil­lion" mes­sage.

                                                                                                                                                                                                                            使用控制总线来管理企业集成系统。控制总线使用与应用程序数据所使用的相同的消息传递机制,但使用单独的通道来传输与消息流中涉及的组件的管理相关的数据。

                                                                                                                                                                                                                            Use a Con­trol Bus to manage an en­ter­prise in­teg­ra­tion system. The Con­trol Bus uses the same mes­saging mech­an­ism used by the ap­plic­a­tion data but uses sep­ar­ate chan­nels to trans­mit data that is rel­ev­ant to the man­age­ment of com­pon­ents in­volved in the mes­sage flow.

                                                                                                                                                                                                                            图形/11inf01.gif



                                                                                                                                                                                                                            系统中的每个组件现在都连接到两个消息传递子系统:

                                                                                                                                                                                                                            Each com­pon­ent in the system is now con­nec­ted to two mes­saging sub­sys­tems:

                                                                                                                                                                                                                            1. 应用程序消息流

                                                                                                                                                                                                                            2. The Ap­plic­a­tion Mes­sage Flow

                                                                                                                                                                                                                            3. 控制总线

                                                                                                                                                                                                                            4. The Con­trol Bus

                                                                                                                                                                                                                            应用程序消息流传输所有与应用程序相关的消息。组件订阅并发布到这些通道,就像在非托管场景中一样。此外,每个组件还从组成控制总线的通道发送和接收消息。这些通道连接到中央管理组件。

                                                                                                                                                                                                                            The ap­plic­a­tion mes­sage flow trans­ports all ap­plic­a­tion-re­lated mes­sages. The com­pon­ents sub­scribe and pub­lish to these chan­nels just as they would in an un­man­aged scen­ario. In ad­di­tion, each com­pon­ent also sends and re­ceives mes­sages from the chan­nels that make up the Con­trol Bus. These chan­nels con­nect to a cent­ral man­age­ment com­pon­ent.

                                                                                                                                                                                                                            控制总线非常适合承载以下类型的消息:

                                                                                                                                                                                                                            The Con­trol Bus is well suited to carry the fol­low­ing types of mes­sages:

                                                                                                                                                                                                                            1. 配置 消息流中涉及的每个 组件应该具有可根据需要进行更改的可配置参数。这些参数包括通道地址、消息数据格式、超时等。组件使用控制总线而不是属性文件从中央存储库检索此信息,从而允许在运行时进行集中配置和重新配置集成解决方案。例如,基于内容的路由器内的路由表可能需要根据系统条件(例如过载或组件故障)动态更新。

                                                                                                                                                                                                                            2. Con­fig­ur­a­tion Each com­pon­ent in­volved in the mes­sage flow should have con­fig­ur­able para­met­ers that can be changed as re­quired. These para­met­ers in­clude chan­nel ad­dresses, mes­sage data formats, timeouts, and so on. Com­pon­ents use the Con­trol Bus rather than prop­erty files to re­trieve this in­form­a­tion from a cent­ral re­pos­it­ory, al­low­ing a cent­ral point of con­fig­ur­a­tion and the re­con­fig­ur­a­tion of the in­teg­ra­tion solu­tion at runtime. For ex­ample, the rout­ing table inside a Con­tent-Based Router may need to be up­dated dy­nam­ic­ally based on system con­di­tions, such as over­load or com­pon­ent fail­ure.

                                                                                                                                                                                                                            3. 心跳 每个组件可以指定的上发送定期心跳消息,以便中央控制台应用程序可以验证该组件是否正常运行。此心跳还可能包括有关组件的度量,例如处理的消息数和机器上的可用内存量。

                                                                                                                                                                                                                            4. Heart­beat Each com­pon­ent may send a peri­odic heart­beat mes­sage on the Con­trol Bus at spe­cified in­ter­vals so that a cent­ral con­sole ap­plic­a­tion can verify that the com­pon­ent is func­tion­ing prop­erly. This heart­beat may also in­clude met­rics about the com­pon­ent, such as number of mes­sages pro­cessed and the amount of avail­able memory on the ma­chine.

                                                                                                                                                                                                                            5. 测试消息 心跳消息告诉控制总线组件仍然处于活动状态,但它们可能提供有关组件正确处理消息的能力的有限信息。除了让组件定期向控制总线发布心跳消息之外,我们还可以将测试消息注入到将由组件处理的消息流中。我们稍后提取消息以查看组件是否正确处理消息。由于这种方法模糊了控制总线和应用程序消息流的定义,我们为其定义了一个单独的模式(请参阅测试消息)

                                                                                                                                                                                                                            6. Test Mes­sages Heart­beat mes­sages tell the Con­trol Bus that a com­pon­ent is still alive, but they may provide lim­ited in­form­a­tion on abil­ity of the com­pon­ent to cor­rectly pro­cess mes­sages. In ad­di­tion to having com­pon­ents pub­lish peri­odic heart­beat mes­sages to the Con­trol Bus, we can inject test mes­sages into the mes­sage stream that will be pro­cessed by the com­pon­ents. We ex­tract the mes­sage later to see whether the com­pon­ent pro­cessed the mes­sage cor­rectly. As this ap­proach blurs the defin­i­tion of the Con­trol Bus and the ap­plic­a­tion mes­sage flow, we defined a sep­ar­ate pat­tern for it (see Test Mes­sage).

                                                                                                                                                                                                                            7. 异常 每个 组件都可以将异常条件传送到控制总线进行评估。严重的异常可能会导致操作员收到警报。定义异常处理的规则应在中央处理程序中指定。

                                                                                                                                                                                                                            8. Ex­cep­tions Each com­pon­ent can chan­nel ex­cep­tion con­di­tions to the Con­trol Bus to be eval­u­ated. Severe ex­cep­tions may cause an op­er­ator to be aler­ted. The rules to define ex­cep­tion hand­ling should be spe­cified in a cent­ral hand­ler.

                                                                                                                                                                                                                            9. 统计信息 每个 组件都可以收集有关处理的消息数量、平均吞吐量、处理消息的平均时间等的统计信息。其中一些数据可能会按消息类型进行拆分,因此我们可以确定某种类型的消息是否正在淹没系统。由于该消息的优先级往往低于其他消息,因此控制总线很可能对此类数据使用无保证或优先级较低的通道。

                                                                                                                                                                                                                            10. Stat­ist­ics Each com­pon­ent can col­lect stat­ist­ics about the number of mes­sages pro­cessed, av­er­age through­put, av­er­age time to pro­cess a mes­sage, and so on. Some of this data may be split out by mes­sage type, so we can de­term­ine whether mes­sages of a cer­tain type are flood­ing the system. Since this mes­sage tends to be lower pri­or­ity than other mes­sages, it is likely that the Con­trol Bus uses nonguar­an­teed or lower-pri­or­ity chan­nels for this type of data.

                                                                                                                                                                                                                            11. 实时控制台 这里提到的 大多数功能都可以聚合在中央控制台中显示。从这里,操作员可以评估消息传递系统的运行状况,并在需要时采取纠正措施。

                                                                                                                                                                                                                            12. Live Con­sole Most of the func­tions men­tioned here can be ag­greg­ated for dis­play in a cent­ral con­sole. From here, op­er­at­ors can assess the health of the mes­saging system and take cor­rect­ive action if needed.

                                                                                                                                                                                                                            控制总线支持的许多功能类似于用于监视和维护任何网络解决方案的传统网络管理功能。控制总线允许我们在消息传递系统级别实现等效的管理功能,从本质上将它们从低级 IP 网络级别提升到更丰富的消息传递级别。提供管理功能对于消息传递基础设施的成功运行与网络基础设施的成功运行一样重要。不幸的是,由于缺乏消息传递解决方案的管理标准,因此很难为消息传递系统构建企业范围内的、可重用的管理解决方案。

                                                                                                                                                                                                                            Many of the func­tions that a Con­trol Bus sup­ports re­semble tra­di­tional net­work man­age­ment func­tions that are used to mon­itor and main­tain any net­worked solu­tion. A Con­trol Bus allows us to im­ple­ment equi­val­ent man­age­ment func­tions at the mes­saging system leve­lessen­tially el­ev­at­ing them from the low-level IP net­work level to the richer mes­saging level. Provid­ing man­age­ment func­tion­al­ity is as vital to the suc­cess­ful op­er­a­tion of a mes­saging in­fra­struc­ture as it is for a net­work in­fra­struc­ture. Un­for­tu­nately, the ab­sence of man­age­ment stand­ards for mes­saging solu­tions makes it dif­fi­cult to build en­ter­prisewide, re­usable man­age­ment solu­tions for mes­saging sys­tems.

                                                                                                                                                                                                                            当我们设计消息处理组件时,我们围绕三个接口构建核心处理器(见图)。入站数据接口接收来自消息通道的传入消息。出站数据接口将处理后的消息发送到出站通道。控制接口向控制总线发送控制消息以及从控制总线接收控制消息。

                                                                                                                                                                                                                            When we design mes­sage pro­cess­ing com­pon­ents, we ar­chi­tect the core pro­cessor around three in­ter­faces (see figure). The in­bound data in­ter­face re­ceives in­com­ing mes­sages from the mes­sage chan­nel. The out­bound data in­ter­face sends pro­cessed mes­sages to the out­bound chan­nel. The con­trol in­ter­face sends and re­ceives con­trol mes­sages from and to the Con­trol Bus.

                                                                                                                                                                                                                            消息传递组件的关键接口

                                                                                                                                                                                                                            Key In­ter­faces of a Mes­saging Com­pon­ent

                                                                                                                                                                                                                            图形/11inf02.gif

                                                                                                                                                                                                                            示例: 贷款经纪人示例

                                                                                                                                                                                                                            Ex­ample: In­stru­ment­ing the Loan Broker Ex­ample

                                                                                                                                                                                                                            第 12 章“插曲:系统管理示例”中,我们展示了如何使用控制总线来检测第 9 章“插曲:组合消息传递”中的贷款经纪人示例。该工具包括一个简单的管理控制台,可以实时显示组件的状态(请参阅第 12 章中的“贷款经纪人系统管理”

                                                                                                                                                                                                                            In Chapter 12, "In­ter­lude: Sys­tems Man­age­ment Ex­ample," we show how to use a Con­trol Bus to in­stru­ment the loan broker ex­ample from Chapter 9, "In­ter­lude: Com­posed Mes­saging." The in­stru­ment­a­tion in­cludes a simple man­age­ment con­sole that dis­plays the status of com­pon­ents in real time (see "Loan Broker System Man­age­ment" in Chapter 12).



                                                                                                                                                                                                                              車輛改道

                                                                                                                                                                                                                              Detour

                                                                                                                                                                                                                              图形/detour_icon.gif

                                                                                                                                                                                                                              有时我们想根据外部因素修改消息的路由。

                                                                                                                                                                                                                              Some­times we want to modify the route that mes­sages take based on ex­ternal factors.

                                                                                                                                                                                                                              如何通过中间步骤路由消息以执行验证、测试或调试功能?

                                                                                                                                                                                                                              How can you route a mes­sage through in­ter­me­di­ate steps to per­form val­id­a­tion, test­ing, or de­bug­ging func­tions?



                                                                                                                                                                                                                              对组件之间传输的消息执行验证可能是一个非常有用的调试工具。然而,这些额外的步骤可能并不总是需要的,并且如果总是执行它们会减慢系统的速度。

                                                                                                                                                                                                                              Per­form­ing val­id­a­tions on mes­sages that travel between com­pon­ents can be a very useful de­bug­ging tool. How­ever, these extra steps may not always be re­quired and would slow down the system if they are always ex­ecuted.

                                                                                                                                                                                                                              能够基于集中设置包含或跳过这些步骤可能是一种非常有效的调试或性能调整工具。例如,当我们测试系统时,我们可能希望通过额外的验证步骤传递消息。在生产过程中绕过这些步骤可能会提高性能。我们可以将这些验证与源代码中的断言语句进行比较,这些语句在调试配置中执行,但不在可执行文件的发布配置中执行。

                                                                                                                                                                                                                              Being able to in­clude or skip these steps based on a cent­ral set­ting can be a very ef­fect­ive de­bug­ging or per­form­ance tuning tool. For ex­ample, while we test a system, we may want to pass mes­sages through ad­di­tional val­id­a­tion steps. By­passing these steps during pro­duc­tion may im­prove per­form­ance. We can com­pare these val­id­a­tions to assert state­ments in source code that are ex­ecuted in the debug con­fig­ur­a­tion but not in the re­lease con­fig­ur­a­tion of the ex­ecut­able.

                                                                                                                                                                                                                              同样,在故障排除期间,通过附加步骤路由消息以用于日志记录或监视目的可能很有用。能够打开和关闭这些日志记录步骤使我们能够在正常情况下最大化消息吞吐量。

                                                                                                                                                                                                                              Like­wise, during troubleshoot­ing, it may be useful to route mes­sages through ad­di­tional steps for log­ging or mon­it­or­ing pur­poses. Being able to turn these log­ging steps on and off allows us to max­im­ize mes­sage through­put under normal cir­cum­stances.

                                                                                                                                                                                                                              使用通过控制总线控制的基于上下文的路由器构造Detour 。在一种状态下,路由器通过附加步骤路由传入消息,而在另一种状态下,路由器将消息直接路由到目标通道。

                                                                                                                                                                                                                              Con­struct a Detour with a Con­text-Based Router con­trolled via the Con­trol Bus. In one state, the router routes in­com­ing mes­sages through ad­di­tional steps, while in the other it routes mes­sages dir­ectly to the des­tin­a­tion chan­nel.

                                                                                                                                                                                                                              图形/11inf03.gif



                                                                                                                                                                                                                              Detour使用一个简单的基于上下文的路由器,具有两个输出通道一个输出通道将未修改的消息传递到原始目的地。当收到控制总线的指示时,Detour将消息路由到不同的通道。该通道将消息发送到可以检查和/或修改消息的其他组件。最终,这些组件将消息路由到同一目的地。

                                                                                                                                                                                                                              The Detour uses a simple con­text-based router with two output chan­nels. One output chan­nel passes the un­mod­i­fied mes­sage to the ori­ginal des­tin­a­tion. When in­struc­ted by the Con­trol Bus, the Detour routes mes­sages to a dif­fer­ent chan­nel. This chan­nel sends the mes­sage to ad­di­tional com­pon­ents that can in­spect and/or modify the mes­sage. Ul­ti­mately, these com­pon­ents route the mes­sage to the same des­tin­a­tion.

                                                                                                                                                                                                                              如果绕行路线仅包含单个组件,则将绕行开关和组件组合到单个过滤器中可能会更有效。然而,该解决方案假设可以修改绕行路径中的组件以包括通过控制总线控制的旁路逻辑。

                                                                                                                                                                                                                              If the detour route con­tains only a single com­pon­ent, it may be more ef­fi­cient to com­bine the Detour switch and the com­pon­ent into a single filter. How­ever, this solu­tion as­sumes that the com­pon­ent in the detour path can be mod­i­fied to in­clude the bypass logic con­trolled via the Con­trol Bus.

                                                                                                                                                                                                                              通过控制总线控制Detour的优势在于,可以使用从控制台到所有Detour 的发布-订阅通道,通过控制总线上的单个命令同时激活或停用多个Detour

                                                                                                                                                                                                                              The strength of con­trolling the Detour over the Con­trol Bus is that mul­tiple Detours can be ac­tiv­ated or de­ac­tiv­ated sim­ul­tan­eously with a single com­mand on the Con­trol Bus using a Pub­lish-Sub­scribe Chan­nel from the con­trol con­sole to all Detours.

                                                                                                                                                                                                                                丝锥

                                                                                                                                                                                                                                Wire Tap

                                                                                                                                                                                                                                图形/wiretap_icon.gif

                                                                                                                                                                                                                                点对点通道通常用于文档消息,因为但是,对于测试、监控或故障排除,能够检查跨通道传输的所有消息可能会很有用。

                                                                                                                                                                                                                                Point-to-Point Chan­nels are often used for Doc­u­ment Mes­sages be­cause they ensure that ex­actly one con­sumer will con­sume each mes­sage. How­ever, for test­ing, mon­it­or­ing, or troubleshoot­ing, it may be useful to be able to in­spect all mes­sages that travel across the chan­nel.

                                                                                                                                                                                                                                如何检查在点对点通道上传输的消息?

                                                                                                                                                                                                                                How do you in­spect mes­sages that travel on a Point-to-Point Chan­nel?



                                                                                                                                                                                                                                查看哪些消息遍历通道非常有用,例如,出于简单的调试目的或将消息存储在消息存储中。您不能只是向点对点通道添加另一个侦听器,因为它会消耗通道外的消息并阻止预期接收者使用该消息。

                                                                                                                                                                                                                                It can be very useful to see which mes­sages tra­verse a chan­nelfor ex­ample, for simple de­bug­ging pur­poses or to store mes­sages in a Mes­sage Store. You can't just add an­other listener to the Point-to-Point Chan­nel, be­cause it would con­sume mes­sages off the chan­nel and pre­vent the in­ten­ded re­cip­i­ent from being able to con­sume the mes­sage.

                                                                                                                                                                                                                                或者,您可以让发送者或接收者负责将消息发布到单独的通道以供检查。然而,这将迫使我们修改一组可能很大的组件。此外,如果我们处理打包的应用程序,我们甚至可能无法修改该应用程序。

                                                                                                                                                                                                                                Al­tern­at­ively, you could make the sender or the re­ceiver re­spons­ible to pub­lish the mes­sage to a sep­ar­ate chan­nel for in­spec­tion. How­ever, this would force us to modify a po­ten­tially large set of com­pon­ents. Ad­di­tion­ally, if we are deal­ing with pack­aged ap­plic­a­tions, we may not even be able to modify the ap­plic­a­tion.

                                                                                                                                                                                                                                您还可以考虑将频道更改为Publish-Subscribe Channel 。这将允许其他侦听器检查消息,而不会干扰消息流。然而,发布-订阅通道改变了通道的语义。例如,多个竞争消费者可能会从通道中消费消息,因为只有一个消费者可以接收特定消息。将频道更改为发布-订阅频道会让每个消费者收到每条消息。例如,如果传入消息代表现在被多次处理的订单,这可能是非常不希望的。即使只有一个消费者在通道上侦听,使用发布-订阅通道也可能比使用点对点通道效率低或可靠性低

                                                                                                                                                                                                                                You could also con­sider chan­ging the chan­nel to a Pub­lish-Sub­scribe Chan­nel. This would allow ad­di­tional listen­ers to in­spect mes­sages without dis­turb­ing the flow of mes­sages. How­ever, a Pub­lish-Sub­scribe Chan­nel changes the se­mantics of the chan­nel. For ex­ample, mul­tiple Com­pet­ing Con­sumers may be con­sum­ing mes­sages off the chan­nel, re­ly­ing on the fact that only one con­sumer can re­ceive a spe­cific mes­sage. Chan­ging the chan­nel to a Pub­lish-Sub­scribe Chan­nel would cause each con­sumer to re­ceive each mes­sage. This could be very un­desir­able, for ex­ample, if the in­com­ing mes­sages rep­res­ent orders that now get pro­cessed mul­tiple times. Even if only a single con­sumer listens on the chan­nel, using a Pub­lish-Sub­scribe Chan­nel may be less ef­fi­cient or less re­li­able than using a Point-to-Point Chan­nel.

                                                                                                                                                                                                                                许多消息传递系统提供了一种 peek 方法,允许组件检查点对点通道内的消息而不消耗该消息。不过,这种方法有一个重要的限制:一旦目标消费者消费了消息,peek 方法就无法再看到该消息。因此,这种方法不允许我们在消息被消费后对其进行分析。

                                                                                                                                                                                                                                Many mes­saging sys­tems provide a peek method that allows a com­pon­ent to in­spect mes­sages inside a Point-to-Point Chan­nel without con­sum­ing the mes­sage. This ap­proach has one im­port­ant lim­it­a­tion though: Once the in­ten­ded con­sumer con­sumes the mes­sage, the peek method can no longer see the mes­sage. There­fore, this ap­proach does not allow us to ana­lyze mes­sages after they have been con­sumed.

                                                                                                                                                                                                                                您可以将一个组件插入到通道中(“拦截器”的一种形式)来执行任何必要的检查。该组件将使用传入通道中的消息,检查该消息,并将未修改的消息传递到输出通道。但是,检查类型经常依赖于来自多个通道的消息(例如,测量消息运行时间),因此这一功能不能通过单个通道内的单个过滤器来实现。

                                                                                                                                                                                                                                You could insert a com­pon­ent into the chan­nel (a form of "in­ter­ceptor') that per­forms any ne­ces­sary in­spec­tion. The com­pon­ent would con­sume a mes­sage off the in­com­ing chan­nel, in­spect the mes­sage, and pass the un­mod­i­fied mes­sage to the output chan­nel. How­ever, the type of in­spec­tion fre­quently de­pends on mes­sages from more than one chan­nel (e.g., to meas­ure mes­sage runtime), so this func­tion cannot be im­ple­men­ted by a single filter inside a single chan­nel.

                                                                                                                                                                                                                                Wire Tap插入通道,这是一个简单的收件人列表,可将每条传入消息发布到主通道以及辅助通道。

                                                                                                                                                                                                                                Insert a Wire Tap into the chan­nel, a simple Re­cip­i­ent List that pub­lishes each in­com­ing mes­sage to the main chan­nel as well as to a sec­ond­ary chan­nel.

                                                                                                                                                                                                                                图形/11inf04.gif



                                                                                                                                                                                                                                Wire Tap(也称为 tee)是具有两个输出通道的固定收件人列表。它消耗输入通道上的消息并将未修改的消息发布到两个输出通道。要将Wire Tap插入通道,您需要创建一个附加通道并更改目标接收器以使用第二个通道。由于分析是由单独的组件执行的,因此我们可以将通用的Wire Tap插入任何通道,而不会产生无意中修改主通道行为的危险。这可以提高重用性并降低在检测现有解决方案时出现不良副作用的风险。

                                                                                                                                                                                                                                The Wire Tap (also known as tee) is a fixed Re­cip­i­ent List with two output chan­nels. It con­sumes mes­sages off the input chan­nel and pub­lishes the un­mod­i­fied mes­sage to both output chan­nels. To insert the Wire Tap into a chan­nel, you need to create an ad­di­tional chan­nel and change the des­tin­a­tion re­ceiver to con­sume the second chan­nel. Be­cause the ana­lysis is per­formed by a sep­ar­ate com­pon­ent, we can insert a gen­eric Wire Tap into any chan­nel without any danger of un­in­ten­tion­ally modi­fy­ing the primary chan­nel be­ha­vior. This im­proves reuse and re­duces the risk of un­desir­able side ef­fects when in­stru­ment­ing an ex­ist­ing solu­tion.

                                                                                                                                                                                                                                通过控制总线对Wire Tap进行编程可能会很有用,以便可以打开或关闭辅助通道(“tap”)。这样,可以指示Wire Tap仅在测试或调试周期期间将消息发布到辅助通道。

                                                                                                                                                                                                                                It might be useful to make the Wire Tap pro­gram­mable over the Con­trol Bus so that the sec­ond­ary chan­nel (the "tap") can be turned on or off. This way, the Wire Tap can be in­struc­ted to pub­lish mes­sages to the sec­ond­ary chan­nel only during test­ing or de­bug­ging cycles.

                                                                                                                                                                                                                                Wire Tap的主要缺点是消费和重新发布消息会产生额外的延迟。许多集成工具套件会自动解码消息,即使该消息未经修改就发布到另一个渠道。此外,新消息将收到与原始消息不同的新消息 ID 和新时间戳。这些操作可能会增加额外的开销并导致现有机制崩溃。例如,如果原始消息流使用原始消息的消息ID作为关联标识符,由于重新发布的消息的消息 ID 与原始消息的消息 ID 不同,解决方案将崩溃​​。这是使用消息 ID 作为关联标识符通常不是一个好主意的原因之一。

                                                                                                                                                                                                                                The main dis­ad­vant­age of the Wire Tap is the ad­di­tional latency in­curred by con­sum­ing and re­pub­lish­ing a mes­sage. Many in­teg­ra­tion tool suites auto­mat­ic­ally decode a mes­sage even if it is pub­lished to an­other chan­nel without modi­fic­a­tion. Also, the new mes­sage will re­ceive a new mes­sage ID and new timestamps that are dif­fer­ent from the ori­ginal mes­sage. These op­er­a­tions can add up to ad­di­tional over­head and cause ex­ist­ing mech­an­isms to break. For ex­ample, if the ori­ginal mes­sage flow uses the mes­sage ID of the ori­ginal mes­sage as a Cor­rel­a­tion Iden­ti­fier, the solu­tion will break be­cause the mes­sage ID of the re­pub­lished mes­sage is dif­fer­ent from the mes­sage ID of the ori­ginal mes­sage. This is one of the reas­ons that it is gen­er­ally not a good idea to use the mes­sage ID as a Cor­rel­a­tion Iden­ti­fier.

                                                                                                                                                                                                                                由于Wire Tap发布两条单独的消息,因此重要的是不要通过消息 ID 来关联这些消息。即使主通道和辅助通道接收相同的消息,大多数消息传递系统也会自动为系统中的每条消息分配一个新的消息 ID。这意味着原始消息和“重复”消息具有不同的消息 ID。

                                                                                                                                                                                                                                Be­cause the Wire Tap pub­lishes two sep­ar­ate mes­sages, it is im­port­ant not to cor­rel­ate between these mes­sages by their mes­sage ID. Even though the primary and the sec­ond­ary chan­nel re­ceive identical mes­sages, most mes­saging sys­tems auto­mat­ic­ally assign a new mes­sage ID to each mes­sage in the system. This means that the ori­ginal mes­sage and the "du­plic­ate" mes­sage have dif­fer­ent mes­sage IDs.

                                                                                                                                                                                                                                现有的Message Broker可以轻松地增强为Wire Tap,因为所有消息都已通过此中央组件。

                                                                                                                                                                                                                                An ex­ist­ing Mes­sage Broker can easily be aug­men­ted to act as a Wire Tap be­cause all mes­sages already pass through this cent­ral com­pon­ent.

                                                                                                                                                                                                                                Wire Tap的一个重要限制是它无法改变流经通道的消息。如果您需要能够操纵消息,请使用Detour

                                                                                                                                                                                                                                An im­port­ant lim­it­a­tion of the Wire Tap is that it cannot alter the mes­sages flow­ing across the chan­nel. If you need to be able to ma­nip­u­late mes­sages, use a Detour in­stead.

                                                                                                                                                                                                                                示例: 贷款经纪人

                                                                                                                                                                                                                                Ex­ample: Loan Broker

                                                                                                                                                                                                                                第 12 章“插曲:系统管理示例”中,我们增强了贷款经纪人示例,在信用局的请求通道上包含 Wire Tap,以记录对此外部服务发出的所有请求(请参阅“贷款经纪人系统管理”) ”,第 12 章)。

                                                                                                                                                                                                                                In Chapter 12, "In­ter­lude: Sys­tems Man­age­ment Ex­ample," we en­hance the loan broker ex­ample to in­clude a Wire Tap on the re­quest chan­nel to the credit bureau to keep a log of all re­quests made to this ex­ternal ser­vice (see "Loan Broker System Man­age­ment" in Chapter 12).



                                                                                                                                                                                                                                示例: 使用多个Wire Tap来测量消息运行时间

                                                                                                                                                                                                                                Ex­ample: Using Mul­tiple Wire Taps to Meas­ure Mes­sage Runtime

                                                                                                                                                                                                                                Wire Tap的优点之一是我们可以组合多个Wire Tap将消息副本发送到中央组件进行分析。该组件可以是消息存储或其他分析消息之间关系的组件,例如两个相关消息之间的时间间隔(见图)。在分析消息运行时间时,我们应该根据次要消息的发送时间进行计算,这样计算就不会因次要消息的传播时间而产生偏差。

                                                                                                                                                                                                                                One of the strengths of the Wire Tap is that we can com­bine mul­tiple Wire Taps to send copies of mes­sages to a cent­ral com­pon­ent for ana­lysis. That com­pon­ent can be a Mes­sage Store or an­other com­pon­ent that ana­lyzes re­la­tion­ships between mes­sages, such as the time in­ter­val between two re­lated mes­sages (see figure). When ana­lyz­ing the mes­sage runtime, we should base the com­pu­ta­tion on the time when the sec­ond­ary mes­sages were sent so that the com­pu­ta­tion is not skewed by the travel time of the sec­ond­ary mes­sages.

                                                                                                                                                                                                                                使用一对丝锥来分析消息运行时间

                                                                                                                                                                                                                                Using a Pair of Wire Taps to Ana­lyze Mes­sage Runtime

                                                                                                                                                                                                                                图形/11inf05.gif



                                                                                                                                                                                                                                  留言记录

                                                                                                                                                                                                                                  Message History

                                                                                                                                                                                                                                  基于消息的系统的主要优点之一是参与者之间的松散耦合。消息发送者和接收者对彼此的身份没有(或很少)做出任何假设。如果消息接收者从消息通道检索消息,它通常不知道也不关心哪个应用程序将消息放入通道上。根据定义,该消息是独立的,并且不与特定的发送者关联。这是基于消息的系统的架构优势之一。

                                                                                                                                                                                                                                  One of key be­ne­fits of a mes­sage-based system is the loose coup­ling between par­ti­cipants; the mes­sage sender and re­cip­i­ent make no (or few) as­sump­tions about each other's iden­tity. If a mes­sage re­cip­i­ent re­trieves a mes­sage from a mes­sage chan­nel, it gen­er­ally does not know nor care which ap­plic­a­tion put the mes­sage on the chan­nel. The mes­sage is by defin­i­tion self-con­tained and is not as­so­ci­ated with a spe­cific sender. This is one of the ar­chi­tec­tural strengths of mes­sage-based sys­tems.

                                                                                                                                                                                                                                  然而,相同的属性可能会使调试和分析依赖关系变得非常困难。如果我们不确定消息的去向,我们如何评估消息格式更改的影响?同样,如果我们不知道哪个应用程序发布了特定消息,则很难纠正该消息的问题。

                                                                                                                                                                                                                                  How­ever, the same prop­erty can make de­bug­ging and ana­lyz­ing de­pend­en­cies very dif­fi­cult. If we are not sure where a mes­sage goes, how can we assess the impact of a change in the mes­sage format? Like­wise, if we don't know which ap­plic­a­tion pub­lished a par­tic­u­lar mes­sage, it is dif­fi­cult to cor­rect a prob­lem with the mes­sage.

                                                                                                                                                                                                                                  如何有效地分析和调试松耦合系统中的消息流?

                                                                                                                                                                                                                                  How can we ef­fect­ively ana­lyze and debug the flow of mes­sages in a loosely coupled system?



                                                                                                                                                                                                                                  控制总线监视处理消息的每个组件的状态,但它不关心单个消息所采用的路由。您可以修改每个组件,以发布通过它传递到控制总线的每条消息的唯一消息标识符。然后可以将这些信息收集到一个公共数据库(消息存储)中。这种方法需要大量的基础设施,包括单独的数据存储。此外,如果组件需要检查消息的历史记录,则必须对中央数据库执行查询,从而存在将数据库变成瓶颈的风险。

                                                                                                                                                                                                                                  The Con­trol Bus mon­it­ors the state of each com­pon­ent that pro­cesses mes­sages, but it does not con­cern itself with the route that an in­di­vidual mes­sage takes. You could modify each com­pon­ent to pub­lish the unique mes­sage iden­ti­fier of each mes­sage that passes through it to the Con­trol Bus. This in­form­a­tion can then be col­lec­ted in a common data­base, a Mes­sage Store. This ap­proach re­quires a sig­ni­fic­ant amount of in­fra­struc­ture, in­clud­ing a sep­ar­ate data­store. Also, if a com­pon­ent needs to ex­am­ine the his­tory of a mes­sage, it would have to ex­ecute a query against a cent­ral data­base, run­ning the risk of turn­ing the data­base into a bot­tle­neck.

                                                                                                                                                                                                                                  跟踪系统中的消息流并不像看上去那么简单。使用与每条消息关联的唯一消息 ID 似乎很自然。然而,当组件(例如,消息路由器)处理消息并将其发布到输出通道时,生成的消息将接收与该组件消耗的消息不关联的新消息标识符。因此,我们需要识别从传入消息复制到传出消息的新密钥,以便稍后可以将这两个消息关联起来。如果组件为其使用的每条消息只发布一条消息,则这种方法可以很好地工作。但是,许多组件的情况并非如此,例如收件人列表聚合器或流程管理器,它们通常发布多条消息以响应单个输入消息。

                                                                                                                                                                                                                                  Track­ing the flow of a mes­sage through a system is not as simple as it ap­pears. It would seem nat­ural to use the unique mes­sage ID as­so­ci­ated with each mes­sage. How­ever, when a com­pon­ent (e.g., a Mes­sage Router) pro­cesses a mes­sage and pub­lishes it to the output chan­nel, the res­ult­ing mes­sage will re­ceive a new mes­sage iden­ti­fier that is not as­so­ci­ated with the mes­sage that the com­pon­ent con­sumed. There­fore, we would need to identify a new key that is copied from the in­com­ing mes­sage to the out­go­ing mes­sage so that the two mes­sages can be as­so­ci­ated later. This can work reas­on­ably well if the com­pon­ent pub­lishes ex­actly one mes­sage for every mes­sage it con­sumes. How­ever, this is not the case for many com­pon­ents, such as a Re­cip­i­ent List, an Ag­greg­ator, or a Pro­cess Man­ager, which typ­ic­ally pub­lish mul­tiple mes­sages in re­sponse to a single input mes­sage.

                                                                                                                                                                                                                                  消息本身可以收集它所遍历的组件列表,而不是通过标记消息来识别每条消息的路径。如果消息传递系统中的每个组件都带有唯一标识符,则每个组件都可以将其标识符添加到其发布的每条消息中。

                                                                                                                                                                                                                                  In­stead of identi­fy­ing the path of each mes­sage by tag­ging the mes­sages, the mes­sage itself could col­lect a list of com­pon­ents that it tra­versed. If each com­pon­ent in the mes­saging system car­ries a unique iden­ti­fier, each com­pon­ent could add its iden­ti­fier to each mes­sage it pub­lishes.

                                                                                                                                                                                                                                  消息历史记录附加到消息中。消息历史记录是消息自发起以来所经过的所有应用程序或组件的列表。

                                                                                                                                                                                                                                  Attach a Mes­sage His­tory to the mes­sage. The Mes­sage His­tory is a list of all ap­plic­a­tions or com­pon­ents that the mes­sage passed through since its ori­gin­a­tion.

                                                                                                                                                                                                                                  图形/11inf06.gif



                                                                                                                                                                                                                                  消息历史记录维护消息所经过的所有组件的列表。每个处理消息的组件(包括发起者)都会向列表中添加一个条目。消息历史记录应该是消息标头的一部分,因为它包含系统特定的控制信息。将此信息保留在标头中可将其与包含应用程序特定数据的消息正文分开。

                                                                                                                                                                                                                                  The Mes­sage His­tory main­tains a list of all com­pon­ents that the mes­sage passed through. Every com­pon­ent that pro­cesses the mes­sage (in­clud­ing the ori­gin­ator) adds one entry to the list. The Mes­sage His­tory should be part of the mes­sage header be­cause it con­tains system-spe­cific con­trol in­form­a­tion. Keep­ing this in­form­a­tion in the header sep­ar­ates it from the mes­sage body that con­tains ap­plic­a­tion-spe­cific data.

                                                                                                                                                                                                                                  并非组件发布的每条消息都是单个消息的结果。例如,聚合器发布一条消息,其中包含从多条消息收集的信息,每条消息都可以有自己的历史记录。如果我们想在消息历史记录中表示这种情况,我们有两种选择。如果我们想跟踪完整的历史记录,我们可以增强消息历史记录存储为分层树结构。由于树结构的递归性质,我们可以在单个节点下存储多个消息历史记录。或者,我们可以保留一个简单的列表并仅保留一条传入消息的历史记录。如果一条传入消息对结果而言比其他辅助消息更重要,则这种方法可以很好地发挥作用。例如,在拍卖场景中,我们可以选择仅传播“获胜”消息的历史记录。

                                                                                                                                                                                                                                  Not every mes­sage that a com­pon­ent pub­lishes is the result of a single mes­sage. For ex­ample, an Ag­greg­ator pub­lishes a single mes­sage that car­ries in­form­a­tion col­lec­ted from mul­tiple mes­sages, each of which could have its own his­tory. If we want to rep­res­ent this scen­ario in the Mes­sage His­tory, we have two choices. If we want to track the com­plete his­tory, we can en­hance the Mes­sage His­tory to be stored as a hier­arch­ical tree struc­ture. Be­cause of the re­curs­ive nature of a tree struc­ture, we can store mul­tiple mes­sage his­tor­ies under a single node. Al­tern­at­ively, we can keep a simple list and keep the his­tory of only one in­com­ing mes­sage. This ap­proach can work well if one in­com­ing mes­sage is more im­port­ant to the result than other, aux­il­i­ary mes­sages. For ex­ample, in an auc­tion scen­ario we could choose to propag­ate only the his­tory of the "win­ning" mes­sage.

                                                                                                                                                                                                                                  如果一系列消息流经多个不同的过滤器,这些过滤器共同执行特定的业务功能或流程,则消息历史记录非常有用如果管理消息所采用的路径很重要,那么流程管理器可能会很有用。流程管理器为每个传入的触发消息创建一个流程实例。现在,可以集中管理通过各个组件的消息流,从而无需使用其历史记录来标记每条消息。

                                                                                                                                                                                                                                  The Mes­sage His­tory is most useful if a series of mes­sages flows through a number of dif­fer­ent fil­ters that to­gether per­form a spe­cific busi­ness func­tion or pro­cess. If it is im­port­ant to manage the path that a mes­sage takes, a Pro­cess Man­ager can be useful. The Pro­cess Man­ager cre­ates one pro­cess in­stance for each in­com­ing trig­ger mes­sage. The flow of the mes­sage through vari­ous com­pon­ents is now man­aged cent­rally, al­le­vi­at­ing the need to tag each mes­sage with its his­tory.

                                                                                                                                                                                                                                  示例: 避免无限循环

                                                                                                                                                                                                                                  Ex­ample: Avoid­ing In­fin­ite Loops

                                                                                                                                                                                                                                  使用发布-订阅通道传播事件时,为消息配备其历史记录还有另一个重要的好处。假设我们实现一个通过发布-订阅通道将地址更改传播到多个系统的系统。每个地址更改都会广播到所有感兴趣的系统,以便它们可以更新其记录。这种方法对于添加新系统非常灵活,新系统将自动接收广播消息,而不需要对现有消息系统进行任何更改。假设客户服务系统是在应用程序数据库中存储地址的系统之一。对数据库字段的每次更改都会触发一条消息,以通知所有系统该更改。根据发布-订阅范例的性质,订阅地址更改通道的所有系统都将接收该事件。然而,客户服务系统本身也必须订阅该频道,以便接收其他系统中所做的更新,例如通过自助服务网站。这意味着客户服务系统将收到它刚刚发布的消息。收到的消息将导致数据库更新,进而触发另一条地址更改消息。我们可能会陷入地址更改消息的无限循环中。为了避免这种无限循环,订阅应用程序可以检查消息历史记录,用于确定消息是否源自同一系统,如果是这种情况,则忽略传入消息。

                                                                                                                                                                                                                                  Equip­ping a mes­sage with its his­tory has an­other im­port­ant be­ne­fit when using Pub­lish-Sub­scribe Chan­nels to propag­ate events. Assume we im­ple­ment a system that propag­ates ad­dress changes to mul­tiple sys­tems via Pub­lish-Sub­scribe Chan­nels. Each ad­dress change is broad­cast to all in­ter­ested sys­tems so that they may update their re­cords. This ap­proach is very flex­ible toward the ad­di­tion of new sys­tem­sthe new system will auto­mat­ic­ally re­ceive the broad­cast mes­sage without re­quir­ing any changes to the ex­ist­ing mes­saging system. Assume the cus­tomer care system is one of the sys­tems that stores ad­dresses in the ap­plic­a­tion data­base. Each change to the data­base fields causes a mes­sage to be triggered to notify all sys­tems of the change. By nature of the pub­lish-sub­scribe paradigm, all sys­tems sub­scrib­ing to the ad­dress-changed chan­nel will re­ceive the event. How­ever, the cus­tomer care system itself has to sub­scribe to this chan­nel as well in order to re­ceive up­dates made in other sys­tems, for ex­ample through a self-ser­vice Web site. This means that the cus­tomer care system will re­ceive the mes­sage that it just pub­lished. This re­ceived mes­sage would result in a data­base update, which will in turn trig­ger an­other ad­dress-changed mes­sage. We could end up in an in­fin­ite loop of ad­dress-changed mes­sages. To avoid such an in­fin­ite loop, the sub­scrib­ing ap­plic­a­tions can in­spect the Mes­sage His­tory to de­term­ine whether the mes­sage ori­gin­ated from the very same system and ignore the in­com­ing mes­sage if this is the case.



                                                                                                                                                                                                                                  示例: TIBCO ActiveEnterprise

                                                                                                                                                                                                                                  Ex­ample: TIBCO Act­iveEn­ter­prise

                                                                                                                                                                                                                                  许多 EAI 集成套件都包含对消息历史记录的支持例如,每个 TIBCO ActiveEnterprise 消息的消息标头都包含一个跟踪字段,该字段维护消息所经过的所有组件的列表。在这种情况下,需要注意的是,TIBCO ActiveEnterprise 组件为传出消息分配与使用的消息相同的消息 ID。这使得通过多个组件跟踪消息变得更容易,但这也意味着消息 ID 不是系统范围内的唯一属性,因为多个单独的消息共享相同的 ID。例如,在实现收件人列表时,TIBCO ActiveEnterprise 将使用的消息的 ID 传输到每个出站消息。

                                                                                                                                                                                                                                  Many EAI in­teg­ra­tion suites in­clude sup­port for a Mes­sage His­tory. For ex­ample, the mes­sage header of every TIBCO Act­iveEn­ter­prise mes­sage in­cludes a track­ing field that main­tains a list of all com­pon­ents through which the mes­sage has passed. In this con­text it is im­port­ant to note that a TIBCO Act­iveEn­ter­prise com­pon­ent as­signs out­go­ing mes­sages the same mes­sage ID as the con­sumed mes­sage. This makes track­ing mes­sages through mul­tiple com­pon­ents easier, but it also means that the mes­sage ID is not a sys­tem­wide unique prop­erty, be­cause mul­tiple in­di­vidual mes­sages share the same ID. For ex­ample, when im­ple­ment­ing a Re­cip­i­ent List, TIBCO Act­iveEn­ter­prise trans­fers the ID of the con­sumed mes­sage to each out­bound mes­sage.

                                                                                                                                                                                                                                  以下示例显示了通过多个组件传递的消息的内容,包括两个名为OrderProcess 和verifyCustomerStub 的IntegrationManager 进程。

                                                                                                                                                                                                                                  The fol­low­ing ex­ample shows the con­tents of a mes­sage that passed through mul­tiple com­pon­ents, in­clud­ing two In­teg­ra­tion­Man­ager pro­cesses, named Or­der­Pro­cess and Veri­fy­Cus­tomer­Stub.

                                                                                                                                                                                                                                  
                                                                                                                                                                                                                                  tw.training.customer.verify.response
                                                                                                                                                                                                                                  {
                                                                                                                                                                                                                                    RVMSG_INT 2 ^pfmt^ 10
                                                                                                                                                                                                                                    RVMSG_INT 2 ^版本^ 30
                                                                                                                                                                                                                                    RVMSG_INT 2 ^类型^ 1
                                                                                                                                                                                                                                    RVMSG_RVMSG 108 ^数据^
                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                      RVMSG_STRING 23 ^class^“验证客户响应”
                                                                                                                                                                                                                                      RVMSG_INT 4 ^idx^ 1
                                                                                                                                                                                                                                      RVMSG_STRING 6 CUSTOMER_ID“12345”
                                                                                                                                                                                                                                      RVMSG_STRING 6 ORDER_ID“22222”
                                                                                                                                                                                                                                      RVMSG_INT 4 结果 0
                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                    RVMSG_RVMSG 150 ^追踪^
                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                      RVMSG_STRING 28 ^id^ "4OEaDEoiBIpcYk6qihzzwB5Uzzw"
                                                                                                                                                                                                                                      RVMSG_STRING 41 ^1^  
                                                                                                                                                                                                                                  图形/ccc.gif“imed_debug_engine1-OrderProcess-Job-4300”
                                                                                                                                                                                                                                      RVMSG_STRING 47 ^2^  
                                                                                                                                                                                                                                  图形/ccc.gif“imed_debug_engine1-VerifyCustomerStub-Job-4301”
                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                  }
                                                                                                                                                                                                                                  
                                                                                                                                                                                                                                  
                                                                                                                                                                                                                                  tw.train­ing.cus­tomer.verify.re­sponse
                                                                                                                                                                                                                                  {
                                                                                                                                                                                                                                    RVMS­G_INT      2  ^pfmt^        10
                                                                                                                                                                                                                                    RVMS­G_INT      2  ^ver^         30
                                                                                                                                                                                                                                    RVMS­G_INT      2  ^type^        1
                                                                                                                                                                                                                                    RVMS­G_R­VMSG  108  ^data^
                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                      RVMS­G_STRING  23  ^class^      "Veri­fy­Cus­tomer­Re­sponse"
                                                                                                                                                                                                                                      RVMS­G_INT      4  ^idx^        1
                                                                                                                                                                                                                                      RVMS­G_STRING   6  CUS­TOM­ER­_ID  "12345"
                                                                                                                                                                                                                                      RVMS­G_STRING   6  OR­DER­_ID     "22222"
                                                                                                                                                                                                                                      RVMS­G_INT      4  RESULT       0
                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                    RVMS­G_R­VMSG  150  ^track­ing^
                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                      RVMS­G_STRING  28  ^id^  "4OE­aD­EoiBIpcYk6qi­hzzw­B5Uzzw"
                                                                                                                                                                                                                                      RVMS­G_STRING  41  ^1^  
                                                                                                                                                                                                                                   "imed_de­bug_en­gine1-Or­der­Pro­cess-Job-4300"
                                                                                                                                                                                                                                      RVMS­G_STRING  47  ^2^  
                                                                                                                                                                                                                                   "imed_de­bug_en­gine1-Veri­fy­Cus­tomer­Stub-Job-4301"
                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                  }
                                                                                                                                                                                                                                  



                                                                                                                                                                                                                                    消息存储

                                                                                                                                                                                                                                    Message Store

                                                                                                                                                                                                                                    图形/messagestore_icon.gif

                                                                                                                                                                                                                                    正如消息历史记录所述,松散耦合的架构原则允许解决方案具有灵活性,但可能会导致难以深入了解集成解决方案的动态行为。

                                                                                                                                                                                                                                    As the Mes­sage His­tory de­scribes, the ar­chi­tec­tural prin­ciple of loose coup­ling allows for flex­ib­il­ity in the solu­tion but can make it dif­fi­cult to gain in­sight into the dy­namic be­ha­vior of the in­teg­ra­tion solu­tion.

                                                                                                                                                                                                                                    我们如何在不影响消息传递系统的松散耦合和瞬态性质的情况下报告消息信息?

                                                                                                                                                                                                                                    How can we report against mes­sage in­form­a­tion without dis­turb­ing the loosely coupled and tran­si­ent nature of a mes­saging system?



                                                                                                                                                                                                                                    使消息传递功能强大的特性也可能使其难以管理。异步消息传递保证传递,但不保证消息何时传递。然而,对于许多实际应用来说,系统的响应时间可能至关重要。此外,虽然异步消息传递单独处理每个消息,但跨越多个消息的信息(例如,在特定时间间隔内通过系统的消息数量)可能非常有用。

                                                                                                                                                                                                                                    The very prop­er­ties that make mes­saging power­ful can also make it dif­fi­cult to manage. Asyn­chron­ous mes­saging guar­an­tees de­liv­ery but does not guar­an­tee when the mes­sage will be de­livered. For many prac­tical ap­plic­a­tions, though, the re­sponse time of a system may be crit­ical. Also, while asyn­chron­ous mes­saging treats each mes­sage in­di­vidu­ally, in­form­a­tion that spans mul­tiple mes­sages­for ex­ample, the number of mes­sages passing through the system within a cer­tain time in­ter­val­can be very useful.

                                                                                                                                                                                                                                    消息历史模式说明了能够辨别消息“来源”的有用性。从这些数据中,我们可以得出有趣的消息吞吐量和运行时统计数据。唯一的缺点是信息包含在每条单独的消息中。没有简单的方法可以报告此信息,因为它分布在许多消息中。此外,消息的生命周期可能非常短。一旦消息被消费,消息历史记录可能不再可用。

                                                                                                                                                                                                                                    The Mes­sage His­tory pat­tern il­lus­trates the use­ful­ness of being able to tell the "source" of a mes­sage. From this data, we can derive in­ter­est­ing mes­sage through­put and runtime stat­ist­ics. The only down­side is that the in­form­a­tion is con­tained within each in­di­vidual mes­sage. There is no easy way to report against this in­form­a­tion, since it is spread across many mes­sages. Also, the life­time of a mes­sage can be very short. Once the mes­sage is con­sumed, the Mes­sage His­tory may no longer be avail­able.

                                                                                                                                                                                                                                    为了执行有意义的报告,我们需要将消息数据持久存储在中央位置。

                                                                                                                                                                                                                                    In order to per­form mean­ing­ful re­port­ing, we need to store mes­sage data per­sist­ently and in a cent­ral loc­a­tion.

                                                                                                                                                                                                                                    使用消息存储在中央位置捕获有关每条消息的信息。

                                                                                                                                                                                                                                    Use a Mes­sage Store to cap­ture in­form­a­tion about each mes­sage in a cent­ral loc­a­tion.

                                                                                                                                                                                                                                    图形/11inf07.gif



                                                                                                                                                                                                                                    使用消息存储时,我们可以利用消息传递基础设施的异步特性。当我们向通道发送消息时,我们会将消息的副本发送到特殊通道以由消息存储收集。这可以由组件本身执行,或者我们可以将丝锥插入通道中。我们可以将携带消息副本的辅助通道视为控制总线的一部分。以“即发即忘”模式发送第二条消息不会减慢主应用程序消息的传输速度。然而,它确实增加了网络流量。这就是为什么我们可能不存储完整的消息,而只存储稍后分析所需的几个关键字段,例如消息 ID 或发送消息的通道与时间戳相结合。

                                                                                                                                                                                                                                    When using a Mes­sage Store, we can take ad­vant­age of the asyn­chron­ous nature of a mes­saging in­fra­struc­ture. When we send a mes­sage to a chan­nel, we send a du­plic­ate of the mes­sage to a spe­cial chan­nel to be col­lec­ted by the Mes­sage Store. This can be per­formed by the com­pon­ent itself, or we can insert a Wire Tap into the chan­nel. We can con­sider the sec­ond­ary chan­nel that car­ries a copy of the mes­sage as part of the Con­trol Bus. Send­ing a second mes­sage in a "fire-and-forget" mode will not slow down the flow of the main ap­plic­a­tion mes­sages. It does, how­ever, in­crease net­work traffic. That's why we may not store the com­plete mes­sage but just a few key fields that are re­quired for later ana­lysis, such as a mes­sage ID or the chan­nel on which the mes­sage was sent com­bined with a timestamp.

                                                                                                                                                                                                                                    存储多少细节实际上是一个重要的考虑因素。显然,我们掌握的每条消息的数据越多,我们的报告能力就越好。反作用力是网络流量和消息存储的存储容量。即使我们存储所有消息数据,我们的报告能力仍然可能受到限制。消息通常共享相同的消息头结构,但每种消息类型的消息正文的结构都不同,并且外部应用程序可能难以访问(例如,消息正文可能包含序列化的 Java 对象)。这可能使得报告消息正文中包含的数据元素变得困难。

                                                                                                                                                                                                                                    How much detail to store is ac­tu­ally an im­port­ant con­sid­er­a­tion. Ob­vi­ously, the more data we have about each mes­sage, the better re­port­ing abil­it­ies we have. The coun­ter­forces are net­work traffic and stor­age ca­pa­city of the Mes­sage Store. Even if we store all mes­sage data, our re­port­ing abil­it­ies may still be lim­ited. Mes­sages typ­ic­ally share the same mes­sage header struc­ture, but the mes­sage body is struc­tured dif­fer­ently for each type of mes­sage and can be dif­fi­cult to access by out­side ap­plic­a­tions (for ex­ample, the mes­sage body might con­tain a seri­al­ized Java object). This can make it dif­fi­cult to report against the data ele­ments con­tained in the mes­sage body.

                                                                                                                                                                                                                                    由于消息正文中的数据对于每种类型的消息可以采用不同的格式,因此我们需要考虑不同的存储选项。如果我们创建一个单独的存储模式(例如,表)来匹配每种消息类型的内部数据结构,我们可以应用索引并对消息内容执行复杂的搜索。然而,这假设我们为每种消息类型都有一个单独的存储结构。这可能很快就会变成维护负担。相反,我们可以将消息数据作为 XML 格式的非结构化数据存储在长字符字段中。这允许我们使用通用存储模式。我们仍然可以查询标头字段,但无法根据消息正文中的字段进行报告。然而,一旦我们确定了一条特定的消息,消息存储。或者,我们可以使用 XML 存储库来存储消息。这些类型的存储库索引 XML 文档以供以后检索和分析。

                                                                                                                                                                                                                                    Since the data inside the mes­sage body can be format­ted dif­fer­ently for each type of mes­sage, we need to con­sider dif­fer­ent stor­age op­tions. If we create a sep­ar­ate stor­age schema (e.g., tables) to match each mes­sage type's in­ternal data struc­ture, we can apply in­dexes and per­form com­plex searches on the mes­sage con­tent. How­ever, this as­sumes that we have a sep­ar­ate stor­age struc­ture for each mes­sage type. This could quickly turn into a main­ten­ance burden. In­stead, we could store the mes­sage data as un­struc­tured data in XML format in a long char­ac­ter field. This allows us to use a gen­eric stor­age schema. We could still query against header fields but would not be able to report against fields in the mes­sage body. How­ever, once we iden­ti­fied a spe­cific mes­sage, we could re­cre­ate the mes­sage con­tent based on the XML doc­u­ment stored in the Mes­sage Store. Al­tern­at­ively, we could use an XML re­pos­it­ory to store the mes­sages. These types of re­pos­it­or­ies index XML doc­u­ments for later re­trieval and ana­lysis.

                                                                                                                                                                                                                                    消息存储可能会变得非常大,因此我们很可能需要引入清除机制。此机制可以将较旧的消息日志移至备份数据库或将其完全删除。

                                                                                                                                                                                                                                    The Mes­sage Store may get very large, so most likely we will need to in­tro­duce a pur­ging mech­an­ism. This mech­an­ism could move older mes­sage logs to a backup data­base or delete them al­to­gether.

                                                                                                                                                                                                                                    示例: 商业 EAI 工具

                                                                                                                                                                                                                                    Ex­ample: Com­mer­cial EAI Tools

                                                                                                                                                                                                                                    一些企业集成工具提供消息存储。例如,MSMQ 允许队列自动将发送或接收的消息存储在Journal Queue中。Microsoft BizTalk 可以选择将所有文档(消息)存储在 SQL Server 数据库中以供以后分析。

                                                                                                                                                                                                                                    Some en­ter­prise in­teg­ra­tion tools supply a Mes­sage Store. For ex­ample, MSMQ allows queues to auto­mat­ic­ally store sent or re­ceived mes­sages in a Journal Queue. Mi­crosoft BizTalk op­tion­ally stores all doc­u­ments (mes­sages) in a SQL Server data­base for later ana­lysis.



                                                                                                                                                                                                                                      智能代理

                                                                                                                                                                                                                                      Smart Proxy

                                                                                                                                                                                                                                      图形/sma​​rtproxy_icon.gif

                                                                                                                                                                                                                                      一对Wire Tap可用于跟踪流经组件的消息,但此方法假设组件将消息发布到固定的输出通道。然而,许多服务样式组件将回复消息发布到请求消息中包含的返回地址指定的通道。

                                                                                                                                                                                                                                      A pair of Wire Taps can be used to track mes­sages that flow through a com­pon­ent, but this ap­proach as­sumes that the com­pon­ent pub­lishes mes­sages to a fixed output chan­nel. How­ever, many ser­vice-style com­pon­ents pub­lish reply mes­sages to the chan­nel spe­cified by the Return Ad­dress in­cluded in the re­quest mes­sage.

                                                                                                                                                                                                                                      如何跟踪向请求者指定的返回地址发布回复消息的服务上的消息?

                                                                                                                                                                                                                                      How can you track mes­sages on a ser­vice that pub­lishes reply mes­sages to the Return Ad­dress spe­cified by the re­questor?



                                                                                                                                                                                                                                      为了跟踪流经服务的消息,我们需要捕获请求和回复消息。使用Wire Tap拦截请求消息非常简单。拦截回复消息是最困难的部分,因为服务根据请求者的首选返回地址将回复消息发布到不同的通道

                                                                                                                                                                                                                                      In order to track mes­sages flow­ing through a ser­vice, we need to cap­ture both re­quest and reply mes­sages. In­ter­cept­ing a re­quest mes­sage using a Wire Tap is easy enough. In­ter­cept­ing reply mes­sages is the tough part be­cause the ser­vice pub­lishes the reply mes­sage to dif­fer­ent chan­nels based on the re­questor's pre­ferred Return Ad­dress.

                                                                                                                                                                                                                                      大多数请求返回地址的支持,以便请求者可以指定回复消息应发送到的通道。更改服务以将回复消息发布到固定通道将使每个请求者很难提取正确的回复消息。某些消息传递系统允许消费者在单个回复队列中查看特定消息,但该方法是特定于实现的,并且在回复消息不返回到请求者而是返回到第三方的情况下不起作用。

                                                                                                                                                                                                                                      The sup­port of a Return Ad­dress is re­quired for most Re­quest-Reply ser­vices so that a re­questor can spe­cify the chan­nel that the reply mes­sage should be sent to. Chan­ging the ser­vice to post reply mes­sages to a fixed chan­nel would make it hard for each re­questor to ex­tract the cor­rect reply mes­sages. Some mes­saging sys­tems allow con­sumers to peek for spe­cific mes­sages inside a single reply queue, but that ap­proach is im­ple­ment­a­tion-spe­cific and does not work in those in­stances where the reply mes­sage does not go back to the re­questor but to a third party.

                                                                                                                                                                                                                                      正如Wire Tap中所讨论的,修改组件来检查消息并不总是可行或实用的。如果我们正在处理打包的应用程序,我们可能无法修改应用程序代码,并且可能必须实现应用程序外部的解决方案。同样,我们可能不希望要求每个应用程序实现消息检查逻辑,特别是因为逻辑的性质可能会根据我们是在测试模式还是生产模式下操作而有所不同。将检查功能保留在单独的、独立的组件中可以提高灵活性、重用性和可测试性。

                                                                                                                                                                                                                                      As dis­cussed in the Wire Tap, modi­fy­ing the com­pon­ent to in­spect mes­sages is not always feas­ible or prac­tical. If we are deal­ing with a pack­aged ap­plic­a­tion, we may not be able to modify the ap­plic­a­tion code and may have to im­ple­ment a solu­tion that is ex­ternal to the ap­plic­a­tion. Like­wise, we may not want to re­quire each ap­plic­a­tion to im­ple­ment mes­sage in­spec­tion logic, es­pe­cially be­cause the nature of the logic may vary de­pend­ing on whether we op­er­ate in test mode or pro­duc­tion mode. Keep­ing the in­spec­tion func­tions in a sep­ar­ate, self-con­tained com­pon­ent im­proves flex­ib­il­ity, reuse, and test­abil­ity.

                                                                                                                                                                                                                                      使用智能代理存储原始请求者提供的返回地址,并将其替换为智能代理的地址。当服务发送回复消息时,将其路由到原始返回地址。

                                                                                                                                                                                                                                      Use a Smart Proxy to store the Return Ad­dress sup­plied by the ori­ginal re­questor and re­place it with the ad­dress of the Smart Proxy. When the ser­vice sends the reply mes­sage, route it to the ori­ginal Return Ad­dress.

                                                                                                                                                                                                                                      图形/11inf08.gif



                                                                                                                                                                                                                                      智能代理拦截在请求通道上发送到请求-答复服务的消息。对于每条传入消息,智能代理都会存储原始发件人指定的返回地址。然后,它将消息中的返回地址替换为智能代理正在侦听的回复通道。当回复消息进入该通道时,智能代理会执行任何所需的分析功能,检索存储的返回地址,并使用消息

                                                                                                                                                                                                                                      The Smart Proxy in­ter­cepts mes­sages sent on the re­quest chan­nel to the Re­quest-Reply ser­vice. For each in­com­ing mes­sage, the Smart Proxy stores the Return Ad­dress spe­cified by the ori­ginal sender. It then re­places the Return Ad­dress in the mes­sage with the reply chan­nel that the Smart Proxy is listen­ing on. When a reply mes­sage comes in on that chan­nel, the Smart Proxy per­forms any de­sired ana­lyt­ical func­tions, re­trieves the stored Return Ad­dress, and for­wards the un­mod­i­fied reply mes­sage to the ori­ginal reply chan­nel by using a Mes­sage Router.

                                                                                                                                                                                                                                      当外部服务不支持返回地址而是回复固定回复通道时,智能代理也很有用。我们可以使用智能代理来代理此类服务,以提供对返回地址的支持。在这种情况下,智能代理不执行任何分析功能,而只是将回复消息转发到正确的通道。

                                                                                                                                                                                                                                      The Smart Proxy is also useful in cases where an ex­ternal ser­vice does not sup­port a Return Ad­dress but in­stead replies to a fixed reply chan­nel. We can proxy such a ser­vice with a Smart Proxy to provide sup­port for a Return Ad­dress. In this case, the Smart Proxy per­forms no ana­lyt­ical func­tions but simply for­wards the reply mes­sage to the cor­rect chan­nel.

                                                                                                                                                                                                                                      智能代理需要存储原始请求者提供的返回地址,以便能够将传入的回复消息与返回地址相关联,并将回复消息转发到正确的通道。智能代理可以将这些数据存储在两个地方:

                                                                                                                                                                                                                                      The Smart Proxy needs to store the Return Ad­dress sup­plied by the ori­ginal re­questor in such a way that it can cor­rel­ate in­com­ing reply mes­sages with the Return Ad­dress and for­ward the reply mes­sage to the cor­rect chan­nel. The Smart Proxy can store this data in two places:

                                                                                                                                                                                                                                      1. 消息里面

                                                                                                                                                                                                                                      2. Inside the mes­sage

                                                                                                                                                                                                                                      3. 智能代理内部

                                                                                                                                                                                                                                      4. Inside the Smart Proxy

                                                                                                                                                                                                                                      为了将返回地址存储在消息中,智能代理可以将新的消息字段与返回地址一起添加到消息中。请求-回复服务需要将此字段复制到回复消息中。智能代理所要做的就是从回复消息中提取特殊的消息字段,从消息中删除该字段,并将消息转发到该字段指定的通道。该解决方案使智能代理保持简单,但它需要请求-答复服务的协作。如果请求-回复服务是不可修改的组件,此选项可能不可用。

                                                                                                                                                                                                                                      To store the Return Ad­dress inside the mes­sage, the Smart Proxy can add a new mes­sage field to­gether with the Return Ad­dress to the mes­sage. The Re­quest-Reply ser­vice is re­quired to copy this field to the reply mes­sage. All the Smart Proxy has to do is ex­tract the spe­cial mes­sage field from the reply mes­sage, remove the field from the mes­sage, and for­ward the mes­sage to the chan­nel spe­cified by the field. This solu­tion keeps the Smart Proxy simple, but it re­quires col­lab­or­a­tion by the Re­quest-Reply ser­vice. If the Re­quest-Reply ser­vice is a non­modi­fi­able com­pon­ent, this option may not be avail­able.

                                                                                                                                                                                                                                      或者,智能代理可以将返回地址存储在专用存储器中,例如存储在存储器结构或关系数据库中。由于智能代理的目的是跟踪请求和回复消息之间的消息,因此智能代理通常必须存储请求消息中的数据,以将其与回复消息关联起来,以便可以同时分析这两个消息。这种方法要求智能代理能够将回复消息与响应消息相关联。大多数请求-答复服务支持关联标识符服务从请求消息复制到回复消息。如果智能代理无法修改原始消息格式,它可以使用(或滥用)此字段来关联请求和回复消息。

                                                                                                                                                                                                                                      Al­tern­at­ively, the Smart Proxy can store the Return Ad­dress in ded­ic­ated stor­age, for ex­ample, in a memory struc­ture or a re­la­tional data­base. Be­cause the pur­pose of the Smart Proxy is to track mes­sages between the re­quest and reply mes­sage, the Smart Proxy usu­ally has to store data from the re­quest mes­sage anyway to cor­rel­ate it to the reply mes­sage so that both mes­sages can be ana­lyzed in unison. This ap­proach re­quires the Smart Proxy to be able to cor­rel­ate the reply mes­sage back to the re­sponse mes­sage. Most Re­quest-Reply ser­vices sup­port a Cor­rel­a­tion Iden­ti­fier that the ser­vice copies from the re­quest mes­sage to the reply mes­sage. If the Smart Proxy cannot modify the ori­ginal mes­sage format, it can use (or abuse) this field to cor­rel­ate re­quest and reply mes­sages.

                                                                                                                                                                                                                                      然而,智能代理最好构建自己的相关标识符,因为并非所有请求者都会指定相关标识符,而且所提供的相关标识符只需要在单个请求者发出的请求中是唯一的,并且可能不是唯一的跨多个请求者。由于从服务到智能代理的单个服务回复队列现在携带来自多个请求者的消息,因此使用原始相关标识符并不可靠。因此,智能代理存储原始的相关标识符与原始返回地址一起,并用自己的相关标识符替换原始相关标识符,以便在回复消息到达时可以检索原始相关标识符和返回地址。

                                                                                                                                                                                                                                      How­ever, it is better for the Smart Proxy to con­struct its own Cor­rel­a­tion Iden­ti­fier, be­cause not all re­questors will spe­cify a Cor­rel­a­tion Iden­ti­fier and also be­cause the sup­plied Cor­rel­a­tion Iden­ti­fier only needs to be unique only across re­quests made by a single re­questor and may not be unique across mul­tiple re­questors. Be­cause the single ser­vice reply queue from the ser­vice to the Smart Proxy now car­ries mes­sages from mul­tiple re­questors, using the ori­ginal Cor­rel­a­tion Iden­ti­fier is not re­li­able. There­fore, the Smart Proxy stores the ori­ginal Cor­rel­a­tion Iden­ti­fier to­gether with the ori­ginal Return Ad­dress and re­places the ori­ginal Cor­rel­a­tion Iden­ti­fier with its own Cor­rel­a­tion Iden­ti­fier so that it can re­trieve the ori­ginal Cor­rel­a­tion Iden­ti­fier and Return Ad­dress when the reply mes­sage ar­rives.

                                                                                                                                                                                                                                      某些服务使用请求消息的消息 ID 作为回复消息的相关标识符。这引入了另一个问题。该服务现在将从智能代理接收到的请求消息的消息 ID 复制到智能代理的回复消息中。智能代理需要将回复消息中的关联标识符替换为原始请求消息的消息ID,以便请求者能够正确关联请求和回复消息。下页的图说明了此过程。

                                                                                                                                                                                                                                      Some ser­vices use the mes­sage ID of the re­quest mes­sage as the Cor­rel­a­tion Iden­ti­fier for the reply mes­sage. This in­tro­duces an­other prob­lem. The ser­vice will now copy the mes­sage ID of the re­quest mes­sage it re­ceived from the Smart Proxy to the reply mes­sage to the Smart Proxy. The Smart Proxy needs to re­place this Cor­rel­a­tion Iden­ti­fier in the reply mes­sage with the mes­sage ID of the ori­ginal re­quest mes­sage so that the re­questor can prop­erly cor­rel­ate re­quest and reply mes­sages. The figure on the fol­low­ing page il­lus­trates this pro­cess.

                                                                                                                                                                                                                                      值得注意的是,所有四个消息都具有唯一的消息 ID,即使它们与单个“逻辑”消息流相关。

                                                                                                                                                                                                                                      It is im­port­ant to note that all four mes­sages have unique mes­sage IDs even though they relate to the flow of a single "lo­gical" mes­sage.

                                                                                                                                                                                                                                      存储和替换相关标识符和返回地址

                                                                                                                                                                                                                                      Stor­ing and Re­pla­cing the Cor­rel­a­tion Iden­ti­fier and Return Ad­dress

                                                                                                                                                                                                                                      图形/11inf09.gif

                                                                                                                                                                                                                                      示例: MSMQ 和 C# 中的简单智能代理

                                                                                                                                                                                                                                      Ex­ample: Simple Smart Proxy in MSMQ and C#

                                                                                                                                                                                                                                      实施智能代理并不像听起来那么复杂。以下代码实现了一个由两个请求者、一个智能代理和一个简单服务组成的解决方案场景。智能代理将消息处理时间传递到控制总线以在控制台中显示。我们希望允许请求者通过消息 ID 或Message对象提供的数字AppSpecific属性进行关联。

                                                                                                                                                                                                                                      Im­ple­ment­ing a Smart Proxy is not as com­plic­ated as it sounds. The fol­low­ing code im­ple­ments a solu­tion scen­ario con­sist­ing of two re­questors, a Smart Proxy and a simple ser­vice. The Smart Proxy passes the mes­sage pro­cess­ing time to the con­trol bus for dis­play in the con­sole. We want to allow the re­questors to cor­rel­ate by either the mes­sage ID or the nu­meric AppSpe­cific prop­erty provided by the Mes­sage object.

                                                                                                                                                                                                                                      简单的智能代理示例

                                                                                                                                                                                                                                      Simple Smart Proxy Ex­ample

                                                                                                                                                                                                                                      图形/11inf10.gif

                                                                                                                                                                                                                                      为了方便编码,我们定义了一个基类MessageConsumer,它封装了创建事件驱动消息使用者所需的代码。继承类可以简单地重载虚拟方法ProcessMessage来执行任何必要的消息处理,并且它们不必担心消息队列的配置或事件驱动的处理。将此代码分离到一个公共基类中,只需几行代码就可以轻松创建测试客户端和虚拟请求-答复服务。

                                                                                                                                                                                                                                      For our coding con­veni­ence, we define a base class Mes­sage­Con­sumer that en­cap­su­lates the code that is re­quired to create an event-driven mes­sage con­sumer. In­her­it­ing classes can simply over­load the vir­tual method Pro­cess­Mes­sage to per­form any ne­ces­sary mes­sage hand­ling, and they do not have to worry about the con­fig­ur­a­tion of the mes­sage queue or the event-driven pro­cess­ing. Sep­ar­at­ing this code into a common base class makes it easy to create test cli­ents and a dummy Re­quest-Reply ser­vice with just a few lines of code.

                                                                                                                                                                                                                                      消息消费者
                                                                                                                                                                                                                                      公共类消息消费者
                                                                                                                                                                                                                                      { 受保护的消息队列输入队列;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          公共消息消费者(消息队列输入队列)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              this.inputQueue = inputQueue;
                                                                                                                                                                                                                                              SetupQueue(this.inputQueue);
                                                                                                                                                                                                                                              Console.WriteLine(this.GetType().Name + ": 处理中
                                                                                                                                                                                                                                      图形/ccc.gif来自“+”的消息
                                                                                                                                                                                                                                                                输入队列.Path);
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          protected void SetupQueue(MessageQueue 队列)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              队列.Formatter = 新的System.Messaging.XmlMessageFormatter
                                                                                                                                                                                                                                                                    (new String[] {"System.String
                                                                                                                                                                                                                                      图形/ccc.gif,mscorlib"});
                                                                                                                                                                                                                                              队列.MessageReadPropertyFilter.ClearAll();
                                                                                                                                                                                                                                              队列.MessageReadPropertyFilter.AppSpecific = true;
                                                                                                                                                                                                                                              队列.MessageReadPropertyFilter.Body = true;
                                                                                                                                                                                                                                              队列.MessageReadPropertyFilter.CorrelationId = true;
                                                                                                                                                                                                                                              队列.MessageReadPropertyFilter.Id = true;
                                                                                                                                                                                                                                              队列.MessageReadPropertyFilter.ResponseQueue = true;
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          公共虚拟无效进程()
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              inputQueue.ReceiveCompleted +=
                                                                                                                                                                                                                                                  新的 ReceiveCompletedEventHandler(OnReceiveCompleted);
                                                                                                                                                                                                                                              inputQueue.BeginReceive();
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          私人无效OnReceiveCompleted(对象源,
                                                                                                                                                                                                                                      图形/ccc.gifReceiveCompletedEventArgs asyncResult)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              消息队列 mq = (消息队列)源;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                              消息 m = mq.EndReceive(asyncResult.AsyncResult);
                                                                                                                                                                                                                                              m.Formatter = 新 System.Messaging.XmlMessageFormatter
                                                                                                                                                                                                                                                                 (new String[] {"System.String,mscorlib"});
                                                                                                                                                                                                                                              处理消息(m);
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                              mq.BeginReceive();
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          protected virtual void ProcessMessage(消息 m)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              字符串文本=“”;
                                                                                                                                                                                                                                              尝试
                                                                                                                                                                                                                                              {
                                                                                                                                                                                                                                                  文本=(字符串)m.Body;
                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                              捕获(InvalidOperationException){};
                                                                                                                                                                                                                                              Console.WriteLine(this.GetType().Name + ": 已收到
                                                                                                                                                                                                                                      图形/ccc.gif消息“+文字);
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                      public class Mes­sage­Con­sumer
                                                                                                                                                                                                                                      {   pro­tec­ted Mes­sageQueue in­putQueue;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          public Mes­sage­Con­sumer (Mes­sageQueue in­putQueue)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              this.in­putQueue = in­putQueue;
                                                                                                                                                                                                                                              SetupQueue(this.in­putQueue);
                                                                                                                                                                                                                                              Con­sole.WriteLine(this.Get­Type().Name +  ": Pro­cess­ing
                                                                                                                                                                                                                                       mes­sages from " +
                                                                                                                                                                                                                                                                in­putQueue.Path);
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          pro­tec­ted void SetupQueue(Mes­sageQueue queue)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              queue.Format­ter = new System.Mes­saging.Xm­lMes­sage­Format­ter
                                                                                                                                                                                                                                                                    (new String[] {"System.String
                                                                                                                                                                                                                                      ,mscorlib"});
                                                                                                                                                                                                                                              queue.Mes­sageRead­Prop­er­ty­Fil­ter.Clear­All();
                                                                                                                                                                                                                                              queue.Mes­sageRead­Prop­er­ty­Fil­ter.AppSpe­cific = true;
                                                                                                                                                                                                                                              queue.Mes­sageRead­Prop­er­ty­Fil­ter.Body = true;
                                                                                                                                                                                                                                              queue.Mes­sageRead­Prop­er­ty­Fil­ter.Cor­rel­a­tionId = true;
                                                                                                                                                                                                                                              queue.Mes­sageRead­Prop­er­ty­Fil­ter.Id = true;
                                                                                                                                                                                                                                              queue.Mes­sageRead­Prop­er­ty­Fil­ter.Re­spon­se­Queue = true;
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          public vir­tual void Pro­cess()
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              in­putQueue.Re­ceive­Com­pleted +=
                                                                                                                                                                                                                                                  new Re­ceive­Com­plete­dE­ventHand­ler(On­Re­ceive­Com­pleted);
                                                                                                                                                                                                                                              in­putQueue.Be­gin­Re­ceive();
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          private void On­Re­ceive­Com­pleted(Object source,
                                                                                                                                                                                                                                       Re­ceive­Com­plete­dEvent­Args asyn­cRes­ult)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              Mes­sageQueue mq = (Mes­sageQueue)source;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                              Mes­sage m = mq.En­dRe­ceive(asyn­cRes­ult.Asyn­cRes­ult);
                                                                                                                                                                                                                                              m.Format­ter =  new System.Mes­saging.Xm­lMes­sage­Format­ter
                                                                                                                                                                                                                                                                 (new String[] {"System.String,mscorlib"});
                                                                                                                                                                                                                                              Pro­cess­Mes­sage(m);
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                              mq.Be­gin­Re­ceive();
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          pro­tec­ted vir­tual void Pro­cess­Mes­sage(Mes­sage m)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              String text = "";
                                                                                                                                                                                                                                              try
                                                                                                                                                                                                                                              {
                                                                                                                                                                                                                                                  text = (String)m.Body;
                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                              catch (In­val­id­Op­er­a­tionEx­cep­tion) {};
                                                                                                                                                                                                                                              Con­sole.WriteLine(this.Get­Type().Name + ": Re­ceived
                                                                                                                                                                                                                                       Mes­sage " + text);
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                      

                                                                                                                                                                                                                                      MessageConsumer 类作为起点,我们可以创建一个智能代理。智能代理包含两个MessageConsumer ,一个用于来自请求者的请求消息 ( SmartProxyRequestConsumer ),另一个用于由请求-回复服务返回的回复消息 ( SmartProxyReplyConsumer ) 。智能代理还定义了一个哈希表来存储请求和回复消息之间的消息数据。

                                                                                                                                                                                                                                      With the Mes­sage­Con­sumer class as a start­ing point, we can create a Smart Proxy. A Smart Proxy con­tains two Mes­sage­Con­sumers, one for the re­quest mes­sages coming from the re­questors (Smart­ProxyRe­quest­Con­sumer) and one for the reply mes­sages re­turned by the Re­quest-Reply ser­vice (Smart­ProxyReply­Con­sumer). The Smart Proxy also defines a Hasht­able to store mes­sage data between re­quest and reply mes­sages.

                                                                                                                                                                                                                                      智能代理
                                                                                                                                                                                                                                      公共类 SmartProxyBase
                                                                                                                                                                                                                                      {
                                                                                                                                                                                                                                          受保护的 SmartProxyRequestConsumer 请求消费者;
                                                                                                                                                                                                                                          受保护的 SmartProxyReplyConsumer 回复Consumer;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          受保护的哈希表消息数据;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          公共SmartProxyBase(消息队列输入队列,
                                                                                                                                                                                                                                                                消息队列服务请求队列,
                                                                                                                                                                                                                                                                消息队列服务回复队列)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              messageData = Hashtable.Synchronized(new Hashtable());
                                                                                                                                                                                                                                              requestConsumer = 新的 SmartProxyRequestConsumer
                                                                                                                                                                                                                                      图形/ccc.gif(输入队列、服务请求队列、
                                                                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                      图形/ccc.gif服务回复队列、消息数据);
                                                                                                                                                                                                                                              回复消费者=新的SmartProxyReplyConsumer
                                                                                                                                                                                                                                      图形/ccc.gif(服务回复队列,消息数据);
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          公共虚拟无效进程()
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              requestConsumer.Process();
                                                                                                                                                                                                                                              回复Consumer.Process();
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                      public class Smart­Proxy­Base
                                                                                                                                                                                                                                      {
                                                                                                                                                                                                                                          pro­tec­ted Smart­ProxyRe­quest­Con­sumer re­quest­Con­sumer;
                                                                                                                                                                                                                                          pro­tec­ted Smart­ProxyReply­Con­sumer reply­Con­sumer;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          pro­tec­ted Hasht­able mes­sageData;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          public Smart­Proxy­Base(Mes­sageQueue in­putQueue,
                                                                                                                                                                                                                                                                Mes­sageQueue ser­vice­Re­questQueue,
                                                                                                                                                                                                                                                                Mes­sageQueue ser­vice­ReplyQueue)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              mes­sageData = Hasht­able.Syn­chron­ized(new Hasht­able());
                                                                                                                                                                                                                                              re­quest­Con­sumer = new Smart­ProxyRe­quest­Con­sumer
                                                                                                                                                                                                                                      (in­putQueue, ser­vice­Re­questQueue,
                                                                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                       ser­vice­ReplyQueue, mes­sageData);
                                                                                                                                                                                                                                              reply­Con­sumer = new Smart­ProxyReply­Con­sumer
                                                                                                                                                                                                                                      (ser­vice­ReplyQueue, mes­sageData);
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          public vir­tual void Pro­cess()
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              re­quest­Con­sumer.Pro­cess();
                                                                                                                                                                                                                                              reply­Con­sumer.Pro­cess();
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                      

                                                                                                                                                                                                                                      SmartProxyRequestConsumer相对简单。它将请求消息中的相关信息(消息 ID、返回地址、 AppSpecific属性和当前时间)存储在哈希表中,并通过发送到实际服务的新请求消息的消息 ID 进行索引。请求-答复服务通过将此消息 ID复制到服务答复消息的CorrelationID 字段来支持相关标识符。这允许智能代理在回复消息到达时检索存储的消息数据。SmartProxyRequestConsumer替换了返回地址ResponseQueue属性以及智能代理侦听回复消息的队列。我们在此类中包含了一个虚拟方法AnalyzeMessage ,以便子类可以执行任何所需的分析。

                                                                                                                                                                                                                                      The Smart­ProxyRe­quest­Con­sumer is re­l­at­ively simple. It stores rel­ev­ant in­form­a­tion from the re­quest mes­sage (mes­sage ID, the Return Ad­dress, the AppSpe­cific prop­erty, and the cur­rent time) in the hasht­able, in­dexed by the mes­sage ID of the new re­quest mes­sage sent to the actual ser­vice. The re­quest-reply ser­vice sup­ports the Cor­rel­a­tion Iden­ti­fier by copy­ing this mes­sage ID to the Cor­rel­a­tionID field of the ser­vice reply mes­sage. This allows the Smart Proxy to re­trieve the stored mes­sage data when the reply mes­sage ar­rives. The Smart­ProxyRe­quest­Con­sumer also re­places the Return Ad­dress the Re­spon­se­Queue prop­er­ty­with the queue that the Smart Proxy listens on for reply mes­sages. We in­cluded a vir­tual method Ana­lyzeMes­sage in this class so that sub­classes can per­form any de­sired ana­lysis.

                                                                                                                                                                                                                                      智能代理请求消费者
                                                                                                                                                                                                                                      公共类 SmartProxyRequestConsumer :MessageConsumer
                                                                                                                                                                                                                                      {
                                                                                                                                                                                                                                          受保护的哈希表消息数据;
                                                                                                                                                                                                                                          受保护的消息队列服务请求队列;
                                                                                                                                                                                                                                          受保护的消息队列服务回复队列;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          公共SmartProxyRequestConsumer(消息队列请求队列,
                                                                                                                                                                                                                                                                           消息队列
                                                                                                                                                                                                                                      图形/ccc.gif服务请求队列,
                                                                                                                                                                                                                                                                           消息队列服务回复队列,
                                                                                                                                                                                                                                                                           哈希表消息数据):
                                                                                                                                                                                                                                      图形/ccc.gif基础(请求队列)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              this.messageData = messageData;
                                                                                                                                                                                                                                              this.serviceRequestQueue = serviceRequestQueue;
                                                                                                                                                                                                                                              this.serviceReplyQueue = serviceReplyQueue;
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          protected override void ProcessMessage(消息 requestMsg)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              基.ProcessMessage(requestMsg);
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                              MessageData 数据 = new MessageData(requestMsg.Id,
                                                                                                                                                                                                                                      图形/ccc.gif请求消息响应队列,
                                                                                                                                                                                                                                                                                 requestMsg.AppSpecific);
                                                                                                                                                                                                                                              requestMsg.ResponseQueue = serviceReplyQueue;
                                                                                                                                                                                                                                              serviceRequestQueue.Send(requestMsg);
                                                                                                                                                                                                                                              messageData.Add(requestMsg.Id, 数据);
                                                                                                                                                                                                                                              分析消息(requestMsg);
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          protected virtual void AnalyzeMessage(消息 requestMsg)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                      public class Smart­ProxyRe­quest­Con­sumer : Mes­sage­Con­sumer
                                                                                                                                                                                                                                      {
                                                                                                                                                                                                                                          pro­tec­ted Hasht­able mes­sageData;
                                                                                                                                                                                                                                          pro­tec­ted Mes­sageQueue ser­vice­Re­questQueue;
                                                                                                                                                                                                                                          pro­tec­ted Mes­sageQueue ser­vice­ReplyQueue;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          public Smart­ProxyRe­quest­Con­sumer(Mes­sageQueue re­questQueue,
                                                                                                                                                                                                                                                                           Mes­sageQueue
                                                                                                                                                                                                                                       ser­vice­Re­questQueue,
                                                                                                                                                                                                                                                                           Mes­sageQueue ser­vice­ReplyQueue,
                                                                                                                                                                                                                                                                           Hasht­able mes­sageData) :
                                                                                                                                                                                                                                       base(re­questQueue)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              this.mes­sageData = mes­sageData;
                                                                                                                                                                                                                                              this.ser­vice­Re­questQueue = ser­vice­Re­questQueue;
                                                                                                                                                                                                                                              this.ser­vice­ReplyQueue = ser­vice­ReplyQueue;
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          pro­tec­ted over­ride void Pro­cess­Mes­sage(Mes­sage re­questMsg)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              base.Pro­cess­Mes­sage(re­questMsg);
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                              Mes­sageData data = new Mes­sageData(re­questMsg.Id,
                                                                                                                                                                                                                                       re­questMsg.Re­spon­se­Queue,
                                                                                                                                                                                                                                                                                 re­questMsg.AppSpe­cific);
                                                                                                                                                                                                                                              re­questMsg.Re­spon­se­Queue = ser­vice­ReplyQueue;
                                                                                                                                                                                                                                              ser­vice­Re­questQueue.Send(re­questMsg);
                                                                                                                                                                                                                                              mes­sageData.Add(re­questMsg.Id, data);
                                                                                                                                                                                                                                              Ana­lyzeMes­sage(re­questMsg);
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          pro­tec­ted vir­tual void Ana­lyzeMes­sage(Mes­sage re­questMsg)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                      

                                                                                                                                                                                                                                      SmartProxyReplyConsumer侦听服务回复通道。此类的ProcessMessage方法检索SmartProxyRequestConsumer,并调用AnalyzeMessage模板方法然后,它将CorrelationIDAppSpecific属性复制到新的回复消息中,并将其路由到原始请求消息中指定的返回地址。

                                                                                                                                                                                                                                      The Smart­ProxyReply­Con­sumer listens on the ser­vice reply chan­nel. The Pro­cess­Mes­sage method of this class re­trieves the mes­sage data for the as­so­ci­ated re­quest mes­sage stored by the Smart­ProxyRe­quest­Con­sumer and calls the Ana­lyzeMes­sage tem­plate method. It then copies the Cor­rel­a­tionID and the AppSpe­cific prop­er­ties to the new reply mes­sage and routes it to the Return Ad­dress spe­cified in the ori­ginal re­quest mes­sage.

                                                                                                                                                                                                                                      智能代理回复消费者
                                                                                                                                                                                                                                      公共类 SmartProxyReplyConsumer :MessageConsumer
                                                                                                                                                                                                                                      {
                                                                                                                                                                                                                                          受保护的哈希表消息数据;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          公共SmartProxyReplyConsumer(消息队列回复队列,
                                                                                                                                                                                                                                                                         哈希表 messageData) : 基础
                                                                                                                                                                                                                                      图形/ccc.gif(回复队列)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              this.messageData = messageData;
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          protected override void ProcessMessage(消息回复Msg)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              基.ProcessMessage(replyMsg);
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                              字符串corr =replyMsg.CorrelationId;
                                                                                                                                                                                                                                              if (messageData.Contains(corr))
                                                                                                                                                                                                                                              {
                                                                                                                                                                                                                                                  MessageData 数据 = (MessageData)(messageData[corr]);
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                  分析消息(数据,回复消息);
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                  回复Msg.CorrelationId = data.CorrelationID;
                                                                                                                                                                                                                                                  回复Msg.AppSpecific = data.AppSpecific;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                  MessageQueue 输出队列 = data.ReturnAddress;
                                                                                                                                                                                                                                                  输出队列.Send(replyMsg);
                                                                                                                                                                                                                                                  messageData.Remove(corr);
                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                              别的
                                                                                                                                                                                                                                              {
                                                                                                                                                                                                                                                  Console.WriteLine(this.GetType().Name + "无法识别
                                                                                                                                                                                                                                      图形/ccc.gif回复消息”);
                                                                                                                                                                                                                                                  //发送消息到无效消息队列
                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          受保护的虚拟无效AnalyzeMessage(MessageData数据,
                                                                                                                                                                                                                                      图形/ccc.gif留言回复留言)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                      public class Smart­ProxyReply­Con­sumer : Mes­sage­Con­sumer
                                                                                                                                                                                                                                      {
                                                                                                                                                                                                                                          pro­tec­ted Hasht­able mes­sageData;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          public Smart­ProxyReply­Con­sumer(Mes­sageQueue replyQueue,
                                                                                                                                                                                                                                                                         Hasht­able mes­sageData) : base
                                                                                                                                                                                                                                      (replyQueue)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              this.mes­sageData = mes­sageData;
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          pro­tec­ted over­ride void Pro­cess­Mes­sage(Mes­sage replyMsg)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              base.Pro­cess­Mes­sage(replyMsg);
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                              String corr = replyMsg.Cor­rel­a­tionId;
                                                                                                                                                                                                                                              if (mes­sageData.Con­tains(corr))
                                                                                                                                                                                                                                              {
                                                                                                                                                                                                                                                  Mes­sageData data = (Mes­sageData)(mes­sageData[corr]);
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                  Ana­lyzeMes­sage(data, replyMsg);
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                  replyMsg.Cor­rel­a­tionId = data.Cor­rel­a­tionID;
                                                                                                                                                                                                                                                  replyMsg.AppSpe­cific = data.AppSpe­cific;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                  Mes­sageQueue out­putQueue = data.Re­tur­nAd­dress;
                                                                                                                                                                                                                                                  out­putQueue.Send(replyMsg);
                                                                                                                                                                                                                                                  mes­sageData.Remove(corr);
                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                              else
                                                                                                                                                                                                                                              {
                                                                                                                                                                                                                                                  Con­sole.WriteLine(this.Get­Type().Name + "Un­re­cog­nized
                                                                                                                                                                                                                                       Reply Mes­sage");
                                                                                                                                                                                                                                                  //send mes­sage to in­valid mes­sage queue
                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          pro­tec­ted vir­tual void Ana­lyzeMes­sage(Mes­sageData data,
                                                                                                                                                                                                                                       Mes­sage replyMes­sage)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                      

                                                                                                                                                                                                                                      为了收集指标并将其发送到控制总线,我们对通用SmartProxySmartProxyReplyConsumer 类进行了子类化。新的MetricsSmartProxySmartProxyReplyConsumerMetrics 实例化为使用。此类包括AnalyzeMessage方法的简单实现,该方法计算请求和响应之间的消息运行时间,并将此数据与未完成消息的数量一起发送到控制总线队列。我们可以轻松地增强此方法以执行更复杂的计算。控制总线队列连接到一个简单的文件编写器,该文件编写器将每个传入消息写入文件。

                                                                                                                                                                                                                                      In order to col­lect met­rics and send them to the con­trol bus, we sub­class both the gen­eric Smart­Proxy and Smart­ProxyReply­Con­sumer classes. The new Met­ric­sS­mart­Proxy in­stan­ti­ates the Smart­ProxyReply­Con­sumer­Met­rics as the class con­sum­ing reply mes­sages. This class in­cludes a simple im­ple­ment­a­tion of the Ana­lyzeMes­sage method that com­putes the mes­sage runtime between re­quest and re­sponse and sends this data to­gether with the number of out­stand­ing mes­sages to the Con­trol Bus queue. We could easily en­hance this method to per­form more com­plex com­pu­ta­tions. The Con­trol Bus queue is con­nec­ted to a simple file writer that writes each in­com­ing mes­sage to a file.

                                                                                                                                                                                                                                      指标SmartProxy
                                                                                                                                                                                                                                      公共类 MetricsSmartProxy :SmartProxyBase
                                                                                                                                                                                                                                      {
                                                                                                                                                                                                                                          公共 MetricsSmartProxy(MessageQueue inputQueue,
                                                                                                                                                                                                                                                                   消息队列服务请求队列,
                                                                                                                                                                                                                                                                   消息队列服务回复队列,
                                                                                                                                                                                                                                                                   消息队列控制总线):
                                                                                                                                                                                                                                                              基(输入队列,服务请求队列,
                                                                                                                                                                                                                                      图形/ccc.gif服务回复队列)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              回复消费者=新的SmartProxyReplyConsumerMetrics
                                                                                                                                                                                                                                                                  (服务回复队列,消息数据,
                                                                                                                                                                                                                                      图形/ccc.gif控制总线);
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                      public class Met­ric­sS­mart­Proxy : Smart­Proxy­Base
                                                                                                                                                                                                                                      {
                                                                                                                                                                                                                                          public Met­ric­sS­mart­Proxy(Mes­sageQueue in­putQueue,
                                                                                                                                                                                                                                                                   Mes­sageQueue ser­vice­Re­questQueue,
                                                                                                                                                                                                                                                                   Mes­sageQueue ser­vice­ReplyQueue,
                                                                                                                                                                                                                                                                   Mes­sageQueue con­trol­Bus) :
                                                                                                                                                                                                                                                              base (in­putQueue, ser­vice­Re­questQueue,
                                                                                                                                                                                                                                       ser­vice­ReplyQueue)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              reply­Con­sumer = new Smart­ProxyReply­Con­sumer­Met­rics
                                                                                                                                                                                                                                                                  (ser­vice­ReplyQueue, mes­sageData,
                                                                                                                                                                                                                                       con­trol­Bus);
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                      SmartProxyReplyConsumerMetrics
                                                                                                                                                                                                                                      公共类 SmartProxyReplyConsumerMetrics :SmartProxyReplyConsumer
                                                                                                                                                                                                                                      {
                                                                                                                                                                                                                                          消息队列控制总线;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          公共 SmartProxyReplyConsumerMetrics(消息队列回复队列,
                                                                                                                                                                                                                                                                                哈希表消息数据,
                                                                                                                                                                                                                                                                                消息队列控制总线):
                                                                                                                                                                                                                                                                           基础(回复队列,消息数据)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              this.controlBus = controlBus;
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          受保护的覆盖无效AnalyzeMessage(MessageData数据,
                                                                                                                                                                                                                                      图形/ccc.gif留言回复留言)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              TimeSpan 持续时间 = DateTime.Now - data.SentTime;
                                                                                                                                                                                                                                              Console.WriteLine("处理时间:{0:f}", 持续时间
                                                                                                                                                                                                                                      图形/ccc.gif.TotalSeconds);
                                                                                                                                                                                                                                              if (controlBus!= null)
                                                                                                                                                                                                                                              {
                                                                                                                                                                                                                                                  controlBus.Send(duration.TotalSeconds.ToString() + "
                                                                                                                                                                                                                                      图形/ccc.gif," + messageData.Count);
                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                      public class Smart­ProxyReply­Con­sumer­Met­rics : Smart­ProxyReply­Con­sumer
                                                                                                                                                                                                                                      {
                                                                                                                                                                                                                                          Mes­sageQueue con­trol­Bus;
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          public Smart­ProxyReply­Con­sumer­Met­rics(Mes­sageQueue replyQueue,
                                                                                                                                                                                                                                                                                Hasht­able mes­sageData,
                                                                                                                                                                                                                                                                                Mes­sageQueue con­trol­Bus) :
                                                                                                                                                                                                                                                                           base(replyQueue, mes­sageData)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              this.con­trol­Bus = con­trol­Bus;
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                          pro­tec­ted over­ride void Ana­lyzeMes­sage(Mes­sageData data,
                                                                                                                                                                                                                                       Mes­sage replyMes­sage)
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                              TimeSpan dur­a­tion = Dat­e­Time.Now - data.Sen­t­Time;
                                                                                                                                                                                                                                              Con­sole.WriteLine(" pro­cess­ing time: {0:f}", dur­a­tion
                                                                                                                                                                                                                                      .TotalSeconds);
                                                                                                                                                                                                                                              if (con­trol­Bus != null)
                                                                                                                                                                                                                                              {
                                                                                                                                                                                                                                                  con­trol­Bus.Send(dur­a­tion.TotalSeconds.To­String() + "
                                                                                                                                                                                                                                      ," + mes­sageData.Count);
                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                      

                                                                                                                                                                                                                                      下面的类图显示了各个类之间的关系:

                                                                                                                                                                                                                                      The fol­low­ing class dia­gram shows the re­la­tion­ship between the in­di­vidual classes:

                                                                                                                                                                                                                                      智能代理示例类图

                                                                                                                                                                                                                                      Smart Proxy Ex­ample Class Dia­gram

                                                                                                                                                                                                                                      图形/11inf11.gif

                                                                                                                                                                                                                                      为了测试代理,我们创建了一个虚拟的请求-答复服务,该服务除了等待 0 到 200 毫秒之间的随机间隔外什么也不做。我们从两个请求者向智能代理提供数据,每个请求者以 100 毫秒的间隔发布 30 条消息。我们将控制总线队列上的消息捕获到日志文件中。出于演示目的,我们将生成的控制总线文件加载到 Microsoft Excel 电子表格中,并创建了一个漂亮的图表。

                                                                                                                                                                                                                                      To test the proxy, we cre­ated a dummy re­quest-reply ser­vice that does noth­ing but wait for a random in­ter­val between 0 and 200 ms. We feed the Smart Proxy from two re­questors, each of which pub­lishes 30 mes­sages in 100 ms in­ter­vals. We cap­ture the mes­sages on the Con­trol Bus queue into a log file. For demon­stra­tion pur­poses, we loaded the res­ult­ing con­trol bus file into a Mi­crosoft Excel spread­sheet and cre­ated a nice look­ing chart.

                                                                                                                                                                                                                                      智能代理收集并由控制总线控制台可视化的响应时间统计数据

                                                                                                                                                                                                                                      Re­sponse Time Stat­ist­ics Col­lec­ted by the Smart Proxy and Visu­al­ized by the Con­trol Bus Con­sole

                                                                                                                                                                                                                                      图形/11inf12.gif

                                                                                                                                                                                                                                      我们可以看到队列大小和响应时间稳步增加,直到 13 条消息排队。此时,请求者停止发送新消息,使得队列大小稳步减小。响应时间也减少了,但仍保持在 1 秒左右,因为现在正在处理的消息已经在请求队列中等待了那么长时间。

                                                                                                                                                                                                                                      We can see that the queue size and the re­sponse time in­crease stead­ily until 13 mes­sages are queued up. At that time, the re­questors stop send­ing new mes­sages so that the queue size de­creases stead­ily. The re­sponse time de­creases as well, but re­mains around 1 second be­cause the mes­sages that are now being pro­cessed have been sit­ting in the re­quest queue for that long.



                                                                                                                                                                                                                                        测试留言

                                                                                                                                                                                                                                        Test Message

                                                                                                                                                                                                                                        图形/testmessage_icon.gif

                                                                                                                                                                                                                                        控制总线描述了多种监视消息处理系统健康状况的方法。系统中的每个组件都可以定期向控制总线发布心跳消息,以便让监控机制了解该组件仍然处于活动状态。心跳消息还可以包含组件的重要统计信息,例如处理的消息数量、处理消息所需的平均时间或计算机上的 CPU 使用率百分比。

                                                                                                                                                                                                                                        The Con­trol Bus de­scribes a number of ap­proaches to mon­itor the health of the mes­sage pro­cess­ing system. Each com­pon­ent in the system can pub­lish peri­odic heart­beat mes­sages to the Con­trol Bus in order to keep the mon­it­or­ing mech­an­ism in­formed that the com­pon­ent is still active. The heart­beat mes­sages can con­tain vital stat­ist­ics of the com­pon­ent as well, such as the number of mes­sages pro­cessed, the av­er­age time re­quired to pro­cess a mes­sage, or the per­cent­age of CPU util­iz­a­tion on the ma­chine.

                                                                                                                                                                                                                                        如果组件正在主动处理消息,但由于内部故障而导致传出消息出现乱码,会发生什么情况?

                                                                                                                                                                                                                                        What hap­pens if a com­pon­ent is act­ively pro­cess­ing mes­sages but garbles out­go­ing mes­sages due to an in­ternal fault?



                                                                                                                                                                                                                                        简单的心跳机制不会检测到此错误情况,因为它仅在组件级别运行并且不知道应用程序消息格式。

                                                                                                                                                                                                                                        A simple heart­beat mech­an­ism will not detect this error con­di­tion be­cause it op­er­ates only at a com­pon­ent level and is not aware of ap­plic­a­tion mes­sage formats.

                                                                                                                                                                                                                                        测试消息注入消息流以确认消息处理组件的运行状况。

                                                                                                                                                                                                                                        Inject a Test Mes­sage into the mes­sage stream to con­firm the health of mes­sage pro­cess­ing com­pon­ents.

                                                                                                                                                                                                                                        图形/11inf13.gif



                                                                                                                                                                                                                                        测试消息模式依赖于以下组件:

                                                                                                                                                                                                                                        The Test Mes­sage pat­tern relies on the fol­low­ing com­pon­ents:

                                                                                                                                                                                                                                        1. 测试数据生成器创建要发送到组件进行测试的消息。测试数据可以是恒定的,由测试数据文件驱动,或者随机生成。

                                                                                                                                                                                                                                        2. The Test Data Gen­er­ator cre­ates mes­sages to be sent to the com­pon­ent for test­ing. Test data may be con­stant, driven by a test data file, or gen­er­ated ran­domly.

                                                                                                                                                                                                                                        3. 测试消息注入器将测试数据插入到发送到组件的常规数据消息流中。注入器的主要作用是标记消息,以便区分真实应用程序消息和测试消息。这可以通过插入特殊的标头字段来完成。如果我们无法控制消息结构,我们可以尝试使用特殊值来指示测试消息(例如,OrderID = 999999)。这通过使用相同的字段来表示应用程序数据(实际订单号)和控制信息(这是测试消息)来改变应用程序数据的语义。因此,这种方法只能作为最后的手段使用。

                                                                                                                                                                                                                                        4. The Test Mes­sage In­jector in­serts test data into the reg­u­lar stream of data mes­sages sent to the com­pon­ent. The main role of the in­jector is to tag mes­sages in order to dif­fer­en­ti­ate real ap­plic­a­tion mes­sages from test mes­sages. This can be ac­com­plished by in­sert­ing a spe­cial header field. If we have no con­trol over the mes­sage struc­ture, we can try to use spe­cial values to in­dic­ate test mes­sages (e.g., Or­derID = 999999). This changes the se­mantics of ap­plic­a­tion data by using the same field to rep­res­ent ap­plic­a­tion data (the actual order number) and con­trol in­form­a­tion (this is a test mes­sage). There­fore, this ap­proach should be used only as a last resort.

                                                                                                                                                                                                                                        5. 测试消息分隔符从输出流中提取测试消息的结果。这通常可以通过使用基于内容的路由器来完成。

                                                                                                                                                                                                                                        6. The Test Mes­sage Sep­ar­ator ex­tracts the res­ults of test mes­sages from the output stream. This can usu­ally be ac­com­plished by using a Con­tent-Based Router.

                                                                                                                                                                                                                                        7. 测试数据验证器将实际结果与预期结果进行比较,如果发现差异,则标记异常。根据测试数据的性质,验证者可能需要访问原始测试数据。

                                                                                                                                                                                                                                        8. The Test Data Veri­fier com­pares actual res­ults with ex­pec­ted res­ults and flags an ex­cep­tion if a dis­crep­ancy is dis­covered. De­pend­ing on the nature of the test data, the veri­fier may need access to the ori­ginal test data.

                                                                                                                                                                                                                                        如果被测组件支持返回地址,则可能不需要显式测试消息分隔符。在这种情况下,测试数据生成器可以包括一个特殊的测试通道作为返回地址,以便测试消息不会通过系统的其余部分传递。实际上,返回地址充当区分测试消息和应用程序消息的标签。

                                                                                                                                                                                                                                        An ex­pli­cit Test Mes­sage Sep­ar­ator may not be needed if the com­pon­ent under test sup­ports a Return Ad­dress. In this case, the test Data Gen­er­ator can in­clude a spe­cial test chan­nel as the Return Ad­dress so that test mes­sages are not passed through the re­mainder of the system. Ef­fect­ively, the Return Ad­dress acts as the tag that dis­tin­guishes test mes­sages from ap­plic­a­tion mes­sages.

                                                                                                                                                                                                                                        测试消息被认为是一种主动监控机制。与被动机制不同,主动机制不依赖于组件生成的信息(例如日志文件或心跳消息),而是主动探测组件。优点是主动监控通常可以实现更深层次的测试,因为数据通过与应用程序消息相同的处理步骤进行路由。它还可以与并非设计用于支持被动监控的组件配合良好。

                                                                                                                                                                                                                                        Test Mes­sage is con­sidered an active mon­it­or­ing mech­an­ism. Unlike pass­ive mech­an­isms, active mech­an­isms do not rely on in­form­a­tion gen­er­ated by the com­pon­ents (e.g., log files or heart­beat mes­sages) but act­ively probe the com­pon­ent. The ad­vant­age is that active mon­it­or­ing usu­ally achieves a deeper level of test­ing, since data is routed through the same pro­cess­ing steps as the ap­plic­a­tion mes­sages. It also works well with com­pon­ents that were not de­signed to sup­port pass­ive mon­it­or­ing.

                                                                                                                                                                                                                                        主动监控的一个可能的缺点是处理单元上的额外负载。我们需要在测试频率和最小化性能影响之间找到平衡。如果我们按按次付费使用组件,主动监控也可能会产生成本。许多外部组件都是这种情况,例如,如果我们向外部信用评分机构请求客户的信用报告。

                                                                                                                                                                                                                                        One pos­sible dis­ad­vant­age of active mon­it­or­ing is the ad­di­tional load placed on the pro­cess­ing unit. We need to find a bal­ance between fre­quency of test and min­im­iz­ing the per­form­ance impact. Active mon­it­or­ing may also incur cost if we are being charged for the use of a com­pon­ent on a pay-per-use basis. This is the case for many ex­ternal com­pon­ents­for ex­ample, if we re­quest credit re­ports for our cus­tom­ers from an ex­ternal credit scor­ing agency.

                                                                                                                                                                                                                                        主动监控并不适用于所有组件。有状态组件可能无法区分测试数据和真实数据,并且可能为测试数据创建数据库条目。我们可能不希望将测试订单包含在我们的年度收入报告中!

                                                                                                                                                                                                                                        Active mon­it­or­ing does not work with all com­pon­ents. State­ful com­pon­ents may not be able to dis­tin­guish test data from real data and may create data­base entries for test data. We may not want to have test orders in­cluded in our annual rev­enue report!

                                                                                                                                                                                                                                        示例: 贷款经纪人:测试信用局

                                                                                                                                                                                                                                        Ex­ample: Loan Broker: Test­ing the Credit Bureau

                                                                                                                                                                                                                                        第 12 章“插曲:系统管理示例”中,我们使用测试消息来主动监控外部信用局。

                                                                                                                                                                                                                                        In Chapter 12, "In­ter­lude: Sys­tems Man­age­ment Ex­ample," we use a Test Mes­sage to act­ively mon­itor the ex­ternal credit bureau.



                                                                                                                                                                                                                                          通道净化器

                                                                                                                                                                                                                                          Channel Purger

                                                                                                                                                                                                                                          图形/channelpurger_icon.gif

                                                                                                                                                                                                                                          当我们研究 JMS 请求-答复示例时(请参阅第 6 章“插曲:简单消息传递”),我们遇到了一个简单但有趣的问题。该示例包含一个请求者,该请求者向回复者发送消息并等待响应。该示例使用两个点对点通道: RequestQueueReplyQueue (见图)。

                                                                                                                                                                                                                                          When we worked on the JMS re­quest-reply ex­ample (see Chapter 6, "In­ter­lude: Simple Mes­saging"), we ran into a simple but in­ter­est­ing prob­lem. The ex­ample con­sists of a re­questor that sends a mes­sage to a replier and waits for the re­sponse. The ex­ample uses two Point-to-Point Chan­nels, Re­questQueue and ReplyQueue (see figure).

                                                                                                                                                                                                                                          图形/11inf14.gif

                                                                                                                                                                                                                                          我们首先启动回复者,然后启动请求者。然后,发生了一件非常奇怪的事情。请求者控制台窗口声称在回复者确认收到请求之前已收到响应。控制台输出有延迟吗?由于缺乏任何好的想法,我们决定关闭回复者,并重新运行请求者。奇怪的是,我们仍然收到了对我们请求的回复!魔法?不,这只是持久消息传递的副作用。ReplyQueue上存在多余的消息,很可能是由之前的故障引起的。每次我们启动请求程序时,它都会在 RequestQueue 上放置一条新消息,然后立即检索ReplyQueue上的无关回复消息。我们从来没有注意到这条消息并不是对请求者刚刚提出的请求的回复!一旦回复者收到新的请求消息,它就会在ReplyQueue上放置一条新的回复消息,以便在下一次测试期间重复“魔法”。即使是在最简单的场景中,持久的异步消息传递也会对您进行欺骗,这可能令人惊讶(或令人惊讶地令人沮丧)!

                                                                                                                                                                                                                                          We star­ted the replier first, then the re­questor. Then, a very odd thing happened. The re­questor con­sole window claimed to have gotten a re­sponse before the replier ever ac­know­ledged re­ceiv­ing a re­quest. A delay in the con­sole output? Lack­ing any great ideas, we de­cided to shut the replier down, and we reran the re­questor. Oddly enough, we still re­ceived a re­sponse to our re­quest! Magic? No, just a side effect of per­sist­ent mes­saging. A su­per­flu­ous mes­sage was present on the ReplyQueue, most likely caused by an earlier fail­ure. Every time we star­ted the re­questor, it placed a new mes­sage on the Re­questQueue and then im­me­di­ately re­trieved the ex­traneous reply mes­sage that was sit­ting on the ReplyQueue. We never no­ticed that this mes­sage was not the reply to the re­quest the re­questor had just made! Once the replier re­ceived the new re­quest mes­sage, it placed a new reply mes­sage on the ReplyQueue so that the "magic" re­peated during the next test. It can be amaz­ing (or amaz­ingly frus­trat­ing) how per­sist­ent, asyn­chron­ous mes­saging can play tricks on you in even the most simple scen­arios!

                                                                                                                                                                                                                                          如何防止通道上的剩余消息干扰测试或正在运行的系统?

                                                                                                                                                                                                                                          How can you keep leftover mes­sages on a chan­nel from dis­turb­ing tests or run­ning sys­tems?



                                                                                                                                                                                                                                          消息渠道即使接收组件不可用,也能可靠地传递消息。为此,通道必须沿途保留消息。这一有用的功能可能会在测试期间或其中一个组件行为不当(并且不使用事务性消息消费和生产)时导致混乱的情况。正如前面所描述的,我们很快就会在通道上遇到无关的消息。这些消息使得在待处理消息被消耗之前无法将测试数据传递到系统中。如果待处理的消息是价值几百万美元的订单,这是一件好事。如果我们正在测试或调试一个系统,并且有一个充满查询消息或回复消息的通道,这可能会给我们带来相当大的麻烦。

                                                                                                                                                                                                                                          Mes­sage Chan­nels are de­signed to de­liver mes­sages re­li­ably even if the re­ceiv­ing com­pon­ent is un­avail­able. In order to do so, the chan­nel has to per­sist mes­sages along the way. This useful fea­ture can cause con­fus­ing situ­ations during test­ing or if one of the com­pon­ents mis­be­haves (and does not use trans­ac­tional mes­sage con­sump­tion and pro­duc­tion). We can quickly end up with ex­traneous mes­sages stuck on chan­nels, as pre­vi­ously de­scribed. These mes­sages make it im­pos­sible to pass test data into the system until the pending mes­sages have been con­sumed. If the pending mes­sages are orders worth a few mil­lion dol­lars, this is a good thing. If we are test­ing or de­bug­ging a system and have a chan­nel full of query mes­sages or reply mes­sages, it can cause us a fair amount of head­ache.

                                                                                                                                                                                                                                          在我们的简单示例中,如果我们使用Correlation Identifier ,我们的一些调试痛苦可能会得到缓解。使用该标识符,请求者将认识到传入消息实际上不是对其刚刚发送的请求的响应。然后,它可以丢弃旧的回复消息或将其路由到无效消息通道,这将有效地消除“卡住”的消息。在其他情况下,检测重复或不需要的消息并不容易。例如,如果特定邮件格式错误并导致邮件收件人失败,则收件人无法重新启动,直到错误邮件被删除,因为它会立即再次失败。当然,此示例需要纠正接收者中的缺陷(格式错误的消息不应导致组件故障),但删除该消息可以使系统快速启动并运行,直到缺陷得到纠正。

                                                                                                                                                                                                                                          In our simple ex­ample, some of our de­bug­ging pain could have been eased if we had used a Cor­rel­a­tion Iden­ti­fier. Using the iden­ti­fier, the re­questor would have re­cog­nized that the in­com­ing mes­sage is ac­tu­ally not the re­sponse to the re­quest it just sent. It could then dis­card the old reply mes­sage or route it to an In­valid Mes­sage Chan­nel, which would ef­fect­ively remove the "stuck" mes­sage. In other scen­arios, it is not as easy to detect du­plic­ate or un­wanted mes­sages. For ex­ample, if a spe­cific mes­sage is mal­formed and causes the mes­sage re­cip­i­ent to fail, the re­cip­i­ent cannot re­start until the bad mes­sage is re­moved, be­cause it would just fail right away again. Of course, this ex­ample re­quires the defect in the re­cip­i­ent to be cor­rec­ted (no mal­formed mes­sage should cause a com­pon­ent fail­ure), but re­mov­ing the mes­sage can get the system up and run­ning quickly until the defect is cor­rec­ted.

                                                                                                                                                                                                                                          避免通道中剩余消息的另一种方法是使用临时通道(例如,JMS为此提供了 createTemporaryQueue方法)。这些通道适用于请求-回复应用程序,一旦应用程序关闭与消息传递系统的连接,它们就会丢失所有消息。但同样,这种方法仅限于简单的请求-答复示例,并且不能防止其他消息留在需要持久化的其他通道上。

                                                                                                                                                                                                                                          An­other way to avoid leftover mes­sages in chan­nels is to use tem­por­ary chan­nels (e.g., JMS provides the method cre­at­eTem­por­aryQueue for this pur­pose). These chan­nels are in­ten­ded for re­quest-reply ap­plic­a­tions and lose all mes­sages once the ap­plic­a­tion closes its con­nec­tion to the mes­saging system. But again, this ap­proach is lim­ited to a simple re­quest-reply ex­ample and does not pro­tect against other mes­sages being left over on other chan­nels that need to be per­sist­ent.

                                                                                                                                                                                                                                          人们可能会认为事务管理可以消除额外的消息场景,因为消息消费、消息处理和消息发布都包含在单个事务中。因此,如果组件在处理消息的过程中中止,则该消息不会被视为已消耗。同样,在组件发出发送消息的最终提交信号之前,不会发布回复消息。但我们需要记住,事务并不能保护我们免受编程错误的影响。在我们简单的请求-回复示例中,程序员错误可能导致请求者无法从 ReplyQueue 读取响应渠道。因此,尽管存在潜在的事务性,一条消息仍滞留在该通道上,从而导致前面描述的症状。

                                                                                                                                                                                                                                          It may be tempt­ing to assume that trans­ac­tion man­age­ment can elim­in­ate the extra mes­sage scen­ario be­cause mes­sage con­sump­tion, mes­sage pro­cess­ing, and mes­sage pub­lic­a­tion are covered in a single trans­ac­tion. So, if a com­pon­ent aborts in the middle of pro­cess­ing a mes­sage, the mes­sage would not be con­sidered con­sumed. Like­wise, a reply mes­sage would not be pub­lished until the com­pon­ent sig­nals the final commit to send the mes­sage. We need to keep in mind, though, that trans­ac­tions do not pro­tect us against pro­gram­ming errors. In our simple re­quest-reply ex­ample, a pro­gram­mer error may have caused the re­questor to not read a re­sponse from the ReplyQueue chan­nel. As a result, des­pite po­ten­tial trans­ac­tion­al­ity, a mes­sage is stuck on that chan­nel, caus­ing the symp­toms de­scribed earlier.

                                                                                                                                                                                                                                          使用通道清除器从通道中删除不需要的消息。

                                                                                                                                                                                                                                          Use a Chan­nel Purger to remove un­wanted mes­sages from a chan­nel.

                                                                                                                                                                                                                                          图形/11inf15.gif



                                                                                                                                                                                                                                          基本的通道清除器只是从通道中删除所有消息。对于我们想要将系统重置为一致状态的测试场景来说,这可能足够了。如果我们正在调试生产系统,我们可能需要根据特定条件(例如消息 ID 或特定消息字段的值)删除单个消息或一组消息。

                                                                                                                                                                                                                                          A basic Chan­nel Purger simply re­moves all the mes­sages from a chan­nel. This may be suf­fi­cient for test scen­arios where we want to reset the system into a con­sist­ent state. If we are de­bug­ging a pro­duc­tion system, we may need to remove an in­di­vidual mes­sage or a set of mes­sages based on spe­cific cri­teria, such as the mes­sage ID or the values of spe­cific mes­sage fields.

                                                                                                                                                                                                                                          在许多情况下,Channel Purger可以简单地将消息从通道中删除并丢弃。在其他情况下,我们可能需要Channel Purger来存储删除的消息以供以后检查或重放。如果通道上的消息导致系统故障,这非常有用,因此我们需要删除它们才能继续操作。但是,一旦问题得到纠正,我们希望重新注入消息,以便系统不会丢失消息的内容。这还可能包括在重新注入消息之前编辑消息内容的要求。这种类型的函数结合了Message StoreChannel Purger的一些功能。

                                                                                                                                                                                                                                          In many cases, it is all right for the Chan­nel Purger to simply delete the mes­sage from the chan­nel and dis­card it. In other cases, we may need the Chan­nel Purger to store the re­moved mes­sages for later in­spec­tion or replay. This is useful if the mes­sages on a chan­nel cause a system to mal­func­tion, so we need to remove them to con­tinue op­er­a­tion. How­ever, once the prob­lems are cor­rec­ted, we want to re-inject the mes­sage(s) so that the system does not lose the con­tents of the mes­sage. This may also in­clude the re­quire­ment to edit mes­sage con­tents before re-in­ject­ing the mes­sage. This type of func­tion com­bines some of the fea­tures of a Mes­sage Store and a Chan­nel Purger.

                                                                                                                                                                                                                                          示例: JMS 中的通道清除器

                                                                                                                                                                                                                                          Ex­ample: Chan­nel Purger in JMS

                                                                                                                                                                                                                                          此示例显示了用 Java 实现的简单Channel Purger。此示例只是删除频道上的所有消息。ChannelPurger类引用了两个外部类,此处未显示其源代码:

                                                                                                                                                                                                                                          This ex­ample shows a simple Chan­nel Purger im­ple­men­ted in Java. This ex­ample simply re­moves all mes­sages on a chan­nel. The Chan­nelPur­ger class ref­er­ences two ex­ternal classes whose source code is not shown here:

                                                                                                                                                                                                                                          1. JMSEndpoint 用于任何 JMS 参与者的 基类。它为Connection 和Session实例提供预初始化的实例变量。

                                                                                                                                                                                                                                          2. JM­SEnd­point A base class to be used for any JMS par­ti­cipant. It provides pre-ini­tial­ized in­stance vari­ables for a Con­nec­tion and Ses­sion in­stance.

                                                                                                                                                                                                                                          3. JNDIUtil 实现帮助器函数来封装通过 JNDI 查找 JMS 对象。

                                                                                                                                                                                                                                          4. JN­DI­Util Im­ple­ments helper func­tions to en­cap­su­late the lookup of JMS ob­jects via JNDI.

                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                          导入 javax.jms.JMSException;
                                                                                                                                                                                                                                          导入 javax.jms.MessageConsumer;
                                                                                                                                                                                                                                          导入javax.jms.Queue;
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                          公共类 ChannelPurger 扩展 JmsEndpoint
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                              公共静态无效主(字符串[]参数)
                                                                                                                                                                                                                                              {
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                  if (args.length != 1) {
                                                                                                                                                                                                                                                      System.out.println("用法:java ChannelPurger
                                                                                                                                                                                                                                          图形/ccc.gif<队列名称>");
                                                                                                                                                                                                                                                      系统.退出(1);
                                                                                                                                                                                                                                                  }
                                                                                                                                                                                                                                                  String 队列名称 = new String(args[0]);
                                                                                                                                                                                                                                                  System.out.println("正在清除队列" + 队列名称);
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                  ChannelPurger 净化器 = new ChannelPurger();
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                  purger.purgeQueue(queueName);
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                              私有无效purgeQueue(字符串队列名称)
                                                                                                                                                                                                                                              {
                                                                                                                                                                                                                                                  尝试 {
                                                                                                                                                                                                                                                      初始化();
                                                                                                                                                                                                                                                      连接.start();
                                                                                                                                                                                                                                                      队列queue = (Queue) JndiUtil.getDestination(queueName);
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                      MessageConsumer 消费者 = session.createConsumer(queue);
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                      while (consumer.receiveNoWait() != null)
                                                                                                                                                                                                                                                          System.out.print(".");
                                                                                                                                                                                                                                                      连接.stop();
                                                                                                                                                                                                                                                  } catch (异常 e) {
                                                                                                                                                                                                                                                      System.out.println("发生异常:" + e
                                                                                                                                                                                                                                          图形/ccc.gif.toString());
                                                                                                                                                                                                                                                  } 最后 {
                                                                                                                                                                                                                                                      如果(连接!=空){
                                                                                                                                                                                                                                                          尝试 {
                                                                                                                                                                                                                                                              连接.close();
                                                                                                                                                                                                                                                          } catch (JMSException e) {
                                                                                                                                                                                                                                                              // 忽略
                                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                                  }
                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                          import javax.jms.JM­SEx­cep­tion;
                                                                                                                                                                                                                                          import javax.jms.Mes­sage­Con­sumer;
                                                                                                                                                                                                                                          import javax.jms.Queue;
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                          public class Chan­nelPur­ger ex­tends Jm­sEnd­point
                                                                                                                                                                                                                                          {
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                              public static void main(String[] args)
                                                                                                                                                                                                                                              {
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                  if (args.length != 1) {
                                                                                                                                                                                                                                                      System.out.println("Usage: java Chan­nelPur­ger
                                                                                                                                                                                                                                           <queue_­name>");
                                                                                                                                                                                                                                                      System.exit(1);
                                                                                                                                                                                                                                                  }
                                                                                                                                                                                                                                                  String queueName = new String(args[0]);
                                                                                                                                                                                                                                                  System.out.println("Pur­ging queue " + queueName);
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                  Chan­nelPur­ger purger = new Chan­nelPur­ger();
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                  purger.purgeQueue(queueName);
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                              private void purgeQueue(String queueName)
                                                                                                                                                                                                                                              {
                                                                                                                                                                                                                                                  try {
                                                                                                                                                                                                                                                      ini­tial­ize();
                                                                                                                                                                                                                                                      con­nec­tion.start();
                                                                                                                                                                                                                                                      Queue queue = (Queue) Jn­di­Util.get­Des­tin­a­tion(queueName);
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                      Mes­sage­Con­sumer con­sumer = ses­sion.cre­ate­Con­sumer(queue);
                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                      while (con­sumer.re­ceiveNoWait() != null)
                                                                                                                                                                                                                                                          System.out.print(".");
                                                                                                                                                                                                                                                      con­nec­tion.stop();
                                                                                                                                                                                                                                                  } catch (Ex­cep­tion e) {
                                                                                                                                                                                                                                                      System.out.println("Ex­cep­tion oc­curred: " + e
                                                                                                                                                                                                                                          .to­String());
                                                                                                                                                                                                                                                  } fi­nally {
                                                                                                                                                                                                                                                      if (con­nec­tion != null) {
                                                                                                                                                                                                                                                          try {
                                                                                                                                                                                                                                                              con­nec­tion.close();
                                                                                                                                                                                                                                                          } catch (JM­SEx­cep­tion e) {
                                                                                                                                                                                                                                                              // ignore
                                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                                      }
                                                                                                                                                                                                                                                  }
                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                          



                                                                                                                                                                                                                                            第 12 章插曲:系统管理示例

                                                                                                                                                                                                                                            Chapter 12. Interlude: System Management Example

                                                                                                                                                                                                                                            贷款经纪人系统管理

                                                                                                                                                                                                                                            Loan Broker System Management

                                                                                                                                                                                                                                            本章使用一个更详细的示例来演示如何使用本节中介绍的系统管理模式来监视和控制消息传递解决方案。该示例基于第 9 章“插曲:组合消息传递”中贷款经纪人示例的 C# 和 MSMQ 实现(请参阅“使用 MSMQ 进行异步实现”)")。我们增强而不是修改原始示例解决方案,因此您查看原始示例的所有代码并不重要。与原始实现一样,此示例的目的不是解释特定于 MSMQ 的使用API 而是为了使用面向队列的消息传递系统来说明本书中模式的实现。当使用 JMS 队列或 IBM WebSphere MQ 在 Java 中实现时,解决方案的结构看起来非常相似。因为我们主要关注设计决策和权衡,即使您不是 C# 或 MSMQ 开发人员,本章也应该对您有价值。

                                                                                                                                                                                                                                            This chapter uses a more elab­or­ate ex­ample to demon­strate how the system man­age­ment pat­terns in­tro­duced in this sec­tion can be used to mon­itor and con­trol a mes­saging solu­tion. The ex­ample builds upon the C# and MSMQ im­ple­ment­a­tion of the loan broker ex­ample from Chapter 9, "In­ter­lude: Com­posed Mes­saging" (see "Asyn­chron­ous Im­ple­ment­a­tion with MSMQ"). We aug­ment rather than modify the ori­ginal ex­ample solu­tion, so it is not crit­ical that you re­viewed all the code of the ori­ginal ex­ample. As with the ori­ginal im­ple­ment­a­tion, the intent of this ex­ample is not to ex­plain the use of MSMQ-spe­cific APIs but rather to il­lus­trate the im­ple­ment­a­tion of the pat­terns in this book using a queue-ori­ented mes­saging system. The struc­ture of the solu­tion would look very sim­ilar when im­ple­men­ted in Java using JMS queues or IBM Web­Sphere MQ. Be­cause we focus mostly on the design de­cisions and trade-offs, this chapter should be valu­able to you even if you are not a C# or MSMQ de­ve­loper.

                                                                                                                                                                                                                                            贷款经纪人的仪器仪表

                                                                                                                                                                                                                                            In­stru­ment­ing the Loan Broker

                                                                                                                                                                                                                                            贷款经纪人实施由以下四个关键组件组成(见图):

                                                                                                                                                                                                                                            The loan broker im­ple­ment­a­tion con­sists of the fol­low­ing four key com­pon­ents (see figure):

                                                                                                                                                                                                                                            图形/12inf01.gif

                                                                                                                                                                                                                                            • 客户(或测试客户)提出贷款报价请求。

                                                                                                                                                                                                                                            • The cus­tomer (or test client) makes re­quests for loan quotes.

                                                                                                                                                                                                                                            • 贷款经纪人充当中央流程管理者,协调征信局和银行之间的沟通。

                                                                                                                                                                                                                                            • The loan broker acts as the cent­ral pro­cess man­ager and co­ordin­ates the com­mu­nic­a­tion between the credit bureau and the banks.

                                                                                                                                                                                                                                            • 信用向贷款经纪人提供服务,计算客户的信用评分。

                                                                                                                                                                                                                                            • The credit bureau provides a ser­vice to the loan broker, com­put­ing cus­tom­ers' credit scores.

                                                                                                                                                                                                                                            • 每家银行都会收到贷款经纪人的报价请求,并根据贷款参数提交利率报价。

                                                                                                                                                                                                                                            • Each bank re­ceives a quote re­quest from the loan broker and sub­mits an in­terest rate quote ac­cord­ing to the loan para­met­ers.

                                                                                                                                                                                                                                            在大多数集成场景中,我们无法访问应用程序内部,只能从外部监视和管理组件。为了使这个示例尽可能真实,我们将每个现有组件视为黑匣子。考虑到这一限制,我们希望管理解决方案满足以下要求:

                                                                                                                                                                                                                                            In most in­teg­ra­tion scen­arios, we do not have access to the ap­plic­a­tion in­tern­als but are lim­ited to mon­it­or­ing and man­aging com­pon­ents from the out­side. To make this ex­ample as real­istic as pos­sible, we treat each of the ex­ist­ing com­pon­ents as a black box. Keep­ing this con­straint in mind, we want the man­age­ment solu­tion to meet the fol­low­ing re­quire­ments:

                                                                                                                                                                                                                                            • 管理控制台: 我们需要一个单一前端来显示所有组件的运行状况,并允许我们在出现问题时采取补偿措施。

                                                                                                                                                                                                                                            • Man­age­ment Con­sole: We want a single front end that dis­plays the health of all com­pon­ents and allows us to take com­pens­at­ing ac­tions if some­thing goes wrong.

                                                                                                                                                                                                                                            • 贷款经纪人服务质量: 在最初的解决方案中,我们开发了一个测试客户端,用于监控贷款经纪人在报价请求和响应之间的响应时间。在真实的生产场景中,客户不会为我们执行此功能(但是他们可能会抱怨系统太慢)。因此,我们希望管理解决方案能够捕获此信息并将其转发到管理控制台。

                                                                                                                                                                                                                                            • Loan Broker Qual­ity of Ser­vice: In the ori­ginal solu­tion, we de­ve­loped a test client that mon­it­ors the loan broker's re­sponse times between quote re­quest and re­sponse. In a real pro­duc­tion scen­ario, cus­tom­ers will not per­form this func­tion for us (they may, how­ever, com­plain that the system is too slow). There­fore, we want the man­age­ment solu­tion to cap­ture this in­form­a­tion and relay it to the man­age­ment con­sole.

                                                                                                                                                                                                                                            • 验证信用局操作: 信用局是第三方提供的外部服务。我们希望通过定期发送测试消息来确保该服务的正确运行。

                                                                                                                                                                                                                                            • Verify the Credit Bureau Op­er­a­tion: The credit bureau is an ex­ternal ser­vice provided by a third party. We want to ensure the cor­rect op­er­a­tion of this ser­vice by peri­od­ic­ally send­ing test mes­sages.

                                                                                                                                                                                                                                            • 信用局故障转移: 如果信用局发生故障,我们希望暂时将信用请求消息重定向到另一个服务提供商。

                                                                                                                                                                                                                                            • Credit Bureau Fail­over: If the credit bureau mal­func­tions, we want to tem­por­ar­ily re­dir­ect the credit re­quest mes­sages to an­other ser­vice pro­vider.

                                                                                                                                                                                                                                            管理控制台

                                                                                                                                                                                                                                            Man­age­ment Con­sole

                                                                                                                                                                                                                                            为了评估整个解决方案的运行状况,我们需要能够将多个组件的指标收集到单个点(管理控制台)。该控制台还必须能够控制消息流和组件参数,以便我们可以通过重新路由消息或更改组件行为来解决中断问题。

                                                                                                                                                                                                                                            To assess the health of the over­all solu­tion, we need to be able to col­lect met­rics from mul­tiple com­pon­ents to a single point, the man­age­ment con­sole. This con­sole also has to be able to con­trol mes­sage flow and com­pon­ent para­met­ers so that we can ad­dress out­ages by rerout­ing mes­sages or chan­ging com­pon­ent be­ha­vior.

                                                                                                                                                                                                                                            管理控制台通过消息传递与各个组件进行通信。它使用单独的控制总线,仅包含与系统管理相关的消息,而不包含与应用程序数据相关的消息。

                                                                                                                                                                                                                                            The man­age­ment con­sole com­mu­nic­ates with the in­di­vidual com­pon­ents via mes­saging. It uses a sep­ar­ate Con­trol Bus that only con­tains mes­sages re­lated to system man­age­ment and not to ap­plic­a­tion data.

                                                                                                                                                                                                                                            因为这是一本关于企业集成而不是用户界面设计的书,所以我们使管理控制台非常非常简单。许多供应商提供实时数据显示,您甚至可以使用 Visual Basic 和 Microsoft Office 组件(例如 Excel)实现视觉奇迹。此外,许多操作系统和编程平台都提供自己的工具框架,例如 Java/JMX(Java 管理扩展)或 Microsoft 的 WMI(Windows 管理工具)。我们手动推出我们的解决方案,以减少其对特定供应商 API 的依赖,并演示监控解决方案的内部工作原理。我们在实现具体的管理功能时,会搭建这个控制台。

                                                                                                                                                                                                                                            Be­cause this is a book on en­ter­prise in­teg­ra­tion and not on user in­ter­face design, we keep the man­age­ment con­sole very, very simple. Many vendors offer real-time data dis­plays, or you can even achieve visual mir­acles with Visual Basic and Mi­crosoft Office com­pon­ents such as Excel. Also, many op­er­at­ing sys­tems and pro­gram­ming plat­forms, offer their own in­stru­ment­a­tion frame­works, such as Java/JMX (Java Man­age­ment Ex­ten­sions) or Mi­crosoft's WMI (Win­dows Man­age­ment In­stru­ment­a­tion). We hand-roll our solu­tion to make it less de­pend­ent on a spe­cific vendor's API and to demon­strate the inner work­ings of a mon­it­or­ing solu­tion. We will build up this con­sole as we im­ple­ment the spe­cific man­age­ment func­tions.

                                                                                                                                                                                                                                            贷款经纪人的服务质量

                                                                                                                                                                                                                                            Loan Broker Qual­ity of Ser­vice

                                                                                                                                                                                                                                            管理解决方案的首要要求是衡量贷款经纪人向客户提供的服务质量。对于这种类型的监控,我们对各个消息的业务内容(即提供给客户端的利率)不感兴趣,而只对请求消息和回复消息之间经过的时间感兴趣。跟踪这两个消息之间的时间的棘手部分是客户端可以通过 Return Address 指定回复消息的通道因此我们无法在固定通道上侦听回复。幸运的是,智能代理模式为我们解决了这个困境。智能代理拦截请求消息,存储返回地址由客户端提供,并替换为固定的回复通道地址。因此,服务(在我们的例子中是贷款经纪人)将所有回复消息发送到一个通道。智能代理侦听该通道并将传入的回复消息与存储的请求消息相关联。然后它将回复消息转发到客户端指定的原始返回地址(见图)。

                                                                                                                                                                                                                                            The first re­quire­ment for the man­age­ment solu­tion is to meas­ure the qual­ity of ser­vice that the loan broker provides to its cli­ents. For this type of mon­it­or­ing, we are not in­ter­ested in the busi­ness con­tent of the in­di­vidual mes­sagesthat is, the in­terest rate offered to the cli­ent­but only in the time elapsed between the re­quest mes­sage and the reply mes­sage. The tricky part in track­ing the time between these two mes­sages is that the client can spe­cify the chan­nel for the reply mes­sage via a Return Ad­dress, so we cannot listen on a fixed chan­nel for the reply. Luck­ily, the Smart Proxy pat­tern solves this di­lemma for us. A Smart Proxy in­ter­cepts a re­quest mes­sage, stores the Return Ad­dress sup­plied by the client, and re­places it with a fixed reply chan­nel ad­dress. As a result, the ser­vice (the loan broker in our case) sends all reply mes­sages to one chan­nel. The Smart Proxy listens to this chan­nel and cor­rel­ates in­com­ing reply mes­sages to stored re­quest mes­sages. It then for­wards the reply mes­sage to the ori­ginal Return Ad­dress spe­cified by the client (see figure).

                                                                                                                                                                                                                                            使用智能代理检测贷款经纪人

                                                                                                                                                                                                                                            In­stru­ment­ing the Loan Broker with a Smart Proxy

                                                                                                                                                                                                                                            图形/12inf02.gif

                                                                                                                                                                                                                                            为了利用智能代理功能,我们在客户和贷款经纪人之间“插入”智能代理(见上图)。此插入对客户端来说是透明的,因为智能代理侦听贷款经纪人最初侦听的同一通道 ( loanRequestQueue) 。现在,我们使用新参数启动贷款经纪人,以便它侦听brokerRequestQueue而不是loanRequestQueue通道。智能代理指示贷款经纪人将所有回复消息发送到BrokerReplyQueue通道,并从该通道将消息转发回正确的位置。最初由客户指定的返回地址。

                                                                                                                                                                                                                                            In order to take ad­vant­age of the Smart Proxy func­tion­al­ity, we "insert" the Smart Proxy between the client and the loan broker (see figure above). This in­ser­tion is trans­par­ent to the client be­cause the Smart Proxy listens on the same chan­nel that the loan broker ori­gin­ally listened on (loan­Re­questQueue). We now start the loan broker with new para­met­ers so that it listens on the broker­Re­questQueue in­stead of the loan­Re­questQueue chan­nel. The Smart Proxy in­structs the loan broker to send all reply mes­sages to the brokerReplyQueue chan­nel from where it for­wards the mes­sages back to the cor­rect Return Ad­dress ori­gin­ally spe­cified by the client.

                                                                                                                                                                                                                                            我们希望使用智能代理来衡量贷款请求的响应时间以及贷款经纪人在任一时间处理的请求数量。智能代理可以通过捕获接收请求消息的时间来测量请求和回复消息之间经过的时间。当智能代理收到关联的回复消息时,它会从当前时间中减去请求时间,以计算请求和回复之间经过的时间。智能代理可以通过计算有多少未完成的请求消息(即尚未收到回复消息的请求消息)来估计贷款经纪人一次管理的活动请求数量。这智能代理无法区分在brokerRequestQueue 上排队的消息和贷款经纪人开始处理的消息,因此该指标等于两者的总和。每当我们收到请求消息或回复消息时,我们都可以更新未完成的请求消息的数量。

                                                                                                                                                                                                                                            We want to use the Smart Proxy to meas­ure both the re­sponse time for loan re­quests and the number of re­quests being pro­cessed by the loan broker at any one time. The Smart Proxy can meas­ure the time elapsed between re­quest and reply mes­sages by cap­tur­ing the time that the re­quest mes­sage was re­ceived. When it re­ceives the as­so­ci­ated reply mes­sage, the Smart Proxy sub­tracts the re­quest time from the cur­rent time to com­pute the time elapsed between re­quest and reply. The Smart Proxy can es­tim­ate how many active re­quests the loan broker is man­aging at one time by count­ing how many out­stand­ing re­quest mes­sages there are (i.e., re­quest mes­sages that have not yet re­ceived reply mes­sages). The Smart Proxy cannot dis­tin­guish between mes­sages queued up on the broker­Re­questQueue and mes­sages that the loan broker star­ted pro­cess­ing, so this metric equals the sum of both. We can update the number of out­stand­ing re­quest mes­sages whenever we re­ceive a re­quest mes­sage or a reply mes­sage.

                                                                                                                                                                                                                                            智能代理通过controlBusQueue通道将指标信息传递到管理控制台进行监控和分析。我们可以发送每条消息的统计信息,但如果我们处理大量消息,这会使我们的网络变得混乱。在消息流中插入智能代理已经使发送的消息数量增加了一倍(两个请求和回复消息,而不是各一个),因此我们希望避免为每个请求消息发送另一个控制消息。相反,我们使用计时器,以便智能代理向控制总线发送指标消息以预定义的时间间隔,例如每 5 秒。度量消息可以包含摘要度量(例如,最大、最小和平均响应时间)或该时间间隔期间通过的所有消息的详细信息。为了保持指标消息较小且管理控制台简单,我们决定仅将摘要指标传递到控制台。

                                                                                                                                                                                                                                            The Smart Proxy passes the met­rics in­form­a­tion to the man­age­ment con­sole for mon­it­or­ing and ana­lysis via the con­trol­BusQueue chan­nel. We could send the stat­ist­ics for every single mes­sage, but that would clut­ter our net­work if we deal with high mes­sage volumes. In­sert­ing a Smart Proxy into the mes­sage flow already doubles the number of mes­sages sent (two re­quest and reply mes­sages in­stead of one each), so we want to avoid send­ing an­other con­trol mes­sage for each re­quest mes­sage. In­stead, we use a timer so that the Smart Proxy sends a met­rics mes­sage to the Con­trol Bus in pre­defined in­ter­vals, for ex­ample, every 5 seconds. The met­rics mes­sage can con­tain either sum­mary met­rics (e.g., the max­imum, min­imum, and av­er­age re­sponse time) or the de­tailed in­form­a­tion for all mes­sages that passed through during the in­ter­val. In order to keep the met­rics mes­sages small and the man­age­ment con­sole simple, we decide to just pass the sum­mary met­rics to the con­sole.

                                                                                                                                                                                                                                            为了实现贷款经纪人智能代理,我们重用智能代理模式中引入的SmartProxy基类。我们对SmartProxyBase 、 SmartProxyRequestConsumer和SmartProxyReplyConsumer类进行子类化(请参阅类图)。请参阅智能代理模式以获取这些类的源代码。

                                                                                                                                                                                                                                            For the im­ple­ment­a­tion of the loan broker Smart Proxy, we reuse the Smart­Proxy base classes in­tro­duced in the Smart Proxy pat­tern. We sub­class the Smart­Proxy­Base, Smart­ProxyRe­quest­Con­sumer, and Smart­ProxyReply­Con­sumer classes (see class dia­gram). Please refer to the Smart Proxy pat­tern for the source code for these classes.

                                                                                                                                                                                                                                            贷款经纪人智能代理类图

                                                                                                                                                                                                                                            Loan Broker Smart Proxy Class Dia­gram

                                                                                                                                                                                                                                            图形/12inf03.gif

                                                                                                                                                                                                                                            就像原来的SmartProxy 一样,新的LoanBrokerProxy包含两个独立的消息使用者,一个用于来自客户端的传入请求消息 ( LoanBrokerProxyRequestConsumer ),另一个用于来自贷款经纪人的传入回复消息 ( LoanBrokerProxyReplyConsumer ) 。这两个使用者类都继承自各自的基类(SmartProxyRequestConsumer 和SmartProxyReplyConsumer ),并添加了AnalyzeMessage方法的新实现

                                                                                                                                                                                                                                            Just like the ori­ginal Smart­Proxy, the new Loan­Broker­Proxy con­tains two sep­ar­ate mes­sage con­sumers, one for in­com­ing re­quest mes­sages from the client (Loan­Broker­ProxyRe­quest­Con­sumer) and one for in­com­ing reply mes­sages from the loan broker (Loan­Broker­ProxyReply­Con­sumer). Both con­sumer classes in­herit from their re­spect­ive base classes (Smart­ProxyRe­quest­Con­sumer and Smart­ProxyReply­Con­sumer) and add a new im­ple­ment­a­tion of the Ana­lyzeMes­sage method.

                                                                                                                                                                                                                                            让我们看一下LoanBrokerProxy类的实现。该构造函数采用与SmartProxyBase 基类相同的参数,以及对控制和以秒为单位的报告间隔。

                                                                                                                                                                                                                                            Let's have a look at the im­ple­ment­a­tion of the Loan­Broker­Proxy class. The con­structor takes the same para­met­ers as the Smart­Proxy­Base base class, plus a ref­er­ence to a Con­trol Bus queue and the re­port­ing in­ter­val in seconds.

                                                                                                                                                                                                                                            该类维护两个带有指标、 PerformanceStats和queueStats的ArrayList PerformanceStats收集响应-回复时间间隔(以秒为单位)的数据点,而queueStats收集未完成请求消息数量的数据点(这些消息在brokerRequestQueue 中排队或由贷款经纪人处理)。当预编程的计时器触发时, OnTimerEvent方法会拍摄两个集合中数据的快照。我们必须使用此快照副本对数据进行进一步的分析,因为消息使用者在收到消息时会继续添加新的数据点。

                                                                                                                                                                                                                                            The class main­tains two Ar­rayL­ists with met­rics, per­form­an­ceStats, and queueStats. per­form­an­ceStats col­lects data points of re­sponse-reply time in­ter­vals in seconds, while queueStats col­lects data points of the number of out­stand­ing re­quest mes­sages (those either queued up in the broker­Re­questQueue or in pro­cess by the loan broker). When the pre­pro­grammed timer trig­gers, the method On­TimerEvent takes a snap­shot of the data in both col­lec­tions. We have to per­form any fur­ther ana­lysis of the data with this snap­shot copy be­cause the mes­sage con­sumers con­tinue to add new data points as mes­sages are re­ceived.

                                                                                                                                                                                                                                            LoanBrokerProxy 类
                                                                                                                                                                                                                                            公共类 LoanBrokerProxy :SmartProxyBase
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                受保护的消息队列控制总线;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                受保护的 ArrayList 性能统计;
                                                                                                                                                                                                                                                受保护的 ArrayList 队列统计信息;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                受保护的 int 区间;
                                                                                                                                                                                                                                                受保护的定时器定时器;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                公共LoanBrokerProxy(消息队列输入队列,消息队列服务请求队列,
                                                                                                                                                                                                                                                                       MessageQueue服务ReplyQueue、MessageQueue控制总线、
                                                                                                                                                                                                                                                                       整数间隔):
                                                                                                                                                                                                                                                    基(输入队列,服务请求队列,服务回复队列)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    messageData = Hashtable.Synchronized(new Hashtable());
                                                                                                                                                                                                                                                    queueStats = ArrayList.Synchronized(new ArrayList());
                                                                                                                                                                                                                                                    PerformanceStats = ArrayList.Synchronized(new ArrayList());
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    this.controlBus = controlBus;
                                                                                                                                                                                                                                                    this.interval = 间隔;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    requestConsumer = new LoanBrokerProxyRequestConsumer(inputQueue,
                                                                                                                                                                                                                                                        服务请求队列、服务回复队列、消息数据、队列统计);
                                                                                                                                                                                                                                                    回复消费者 = 新 LoanBrokerProxyReplyConsumer(serviceReplyQueue,
                                                                                                                                                                                                                                                        消息数据、队列统计、性能统计);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                公共重写 void Process()
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    基.进程();
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    TimerCallback timeDelegate = new TimerCallback(OnTimerEvent);
                                                                                                                                                                                                                                                    计时器=新计时器(timerDelegate,空,间隔* 1000,间隔* 1000);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                protected void OnTimerEvent(对象状态)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    ArrayList 当前队列统计信息;
                                                                                                                                                                                                                                                    ArrayList 当前性能统计;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    锁(队列统计)
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        currentQueueStats = (ArrayList)(queueStats.Clone());
                                                                                                                                                                                                                                                        队列统计.Clear();
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    锁定(性能统计)
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        currentPerformanceStats = (ArrayList)(performanceStats.Clone());
                                                                                                                                                                                                                                                        性能统计.Clear();
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    SummaryStats 摘要 = new SummaryStats(currentQueueStats,
                                                                                                                                                                                                                                                                                            当前性能统计);
                                                                                                                                                                                                                                                    if (controlBus!= null)
                                                                                                                                                                                                                                                        controlBus.Send(摘要);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            public class Loan­Broker­Proxy : Smart­Proxy­Base
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                pro­tec­ted Mes­sageQueue con­trol­Bus;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                pro­tec­ted Ar­rayL­ist per­form­an­ceStats;
                                                                                                                                                                                                                                                pro­tec­ted Ar­rayL­ist queueStats;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                pro­tec­ted int in­ter­val;
                                                                                                                                                                                                                                                pro­tec­ted Timer timer;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                public Loan­Broker­Proxy(Mes­sageQueue in­putQueue, Mes­sageQueue ser­vice­Re­questQueue,
                                                                                                                                                                                                                                                                       Mes­sageQueue ser­vice­ReplyQueue, Mes­sageQueue con­trol­Bus,
                                                                                                                                                                                                                                                                       int in­ter­val) :
                                                                                                                                                                                                                                                    base (in­putQueue, ser­vice­Re­questQueue, ser­vice­ReplyQueue)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    mes­sageData = Hasht­able.Syn­chron­ized(new Hasht­able());
                                                                                                                                                                                                                                                    queueStats = Ar­rayL­ist.Syn­chron­ized(new Ar­rayL­ist());
                                                                                                                                                                                                                                                    per­form­an­ceStats = Ar­rayL­ist.Syn­chron­ized(new Ar­rayL­ist());
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    this.con­trol­Bus = con­trol­Bus;
                                                                                                                                                                                                                                                    this.in­ter­val = in­ter­val;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    re­quest­Con­sumer = new Loan­Broker­ProxyRe­quest­Con­sumer(in­putQueue,
                                                                                                                                                                                                                                                        ser­vice­Re­questQueue, ser­vice­ReplyQueue, mes­sageData, queueStats);
                                                                                                                                                                                                                                                    reply­Con­sumer = new Loan­Broker­ProxyReply­Con­sumer(ser­vice­ReplyQueue,
                                                                                                                                                                                                                                                        mes­sageData, queueStats, per­form­an­ceStats);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                public over­ride void Pro­cess()
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    base.Pro­cess();
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    Timer­Call­back timer­Deleg­ate = new Timer­Call­back(On­TimerEvent);
                                                                                                                                                                                                                                                    timer = new Timer(timer­Deleg­ate, null, in­ter­val*1000, in­ter­val*1000);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                pro­tec­ted void On­TimerEvent(Object state)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    Ar­rayL­ist cur­rentQueueStats;
                                                                                                                                                                                                                                                    Ar­rayL­ist cur­rent­Per­form­an­ceStats;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    lock (queueStats)
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        cur­rentQueueStats = (Ar­rayL­ist)(queueStats.Clone());
                                                                                                                                                                                                                                                        queueStats.Clear();
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    lock (per­form­an­ceStats)
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        cur­rent­Per­form­an­ceStats = (Ar­rayL­ist)(per­form­an­ceStats.Clone());
                                                                                                                                                                                                                                                        per­form­an­ceStats.Clear();
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    Sum­maryS­tats sum­mary = new Sum­maryS­tats(cur­rentQueueStats,
                                                                                                                                                                                                                                                                                            cur­rent­Per­form­an­ceStats);
                                                                                                                                                                                                                                                    if (con­trol­Bus != null)
                                                                                                                                                                                                                                                        con­trol­Bus.Send(sum­mary);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            

                                                                                                                                                                                                                                            LoanBrokerProxy使用SummaryStats结构将各个数据点压缩为最大值、最小值和平均值,然后将摘要数据发送到控制总线。 我们可以通过更新每条传入消息的摘要统计信息来提高评估效率,这样我们就只需存储摘要数据而不是每个数据点。但是,推迟计算允许我们更改要发布到控制总线的详细信息量。

                                                                                                                                                                                                                                            The Loan­Broker­Proxy uses the Sum­maryS­tats struc­ture to con­dense the in­di­vidual data points into max­imum, min­imum, and av­er­age values and then sends the sum­mary data to the Con­trol Bus. We could make the eval­u­ation more ef­fi­cient by up­dat­ing the sum­mary stat­ist­ics with each in­com­ing mes­sage so that we have to store only the sum­mary data and not each data point. How­ever, de­fer­ring the com­pu­ta­tion allows us to change the amount of detail we want to pub­lish to the Con­trol Bus.

                                                                                                                                                                                                                                            LoanBrokerProxyRequestConsumer处理传入的请求消息。基类SmartProxyRequestConsumer负责将相关消息数据存储在messageData 哈希表中。同样,SmartProxyReplyConsumer 的基本实现每当收到回复消息时都会从该哈希表中删除数据。因此,我们可以从messageData哈希表的大小得出当前未完成的请求消息的数量。LoanBrokerProxyRequestConsumer维护对存储在LoanBrokerProxyqueueStats集合的引用以便它可以将新数据点添加到该集合中。

                                                                                                                                                                                                                                            The Loan­Broker­ProxyRe­quest­Con­sumer class handles in­com­ing re­quest mes­sages. The base class Smart­ProxyRe­quest­Con­sumer takes care of stor­ing rel­ev­ant mes­sage data in the mes­sageData hash table. Like­wise, the base im­ple­ment­a­tion of the Smart­ProxyReply­Con­sumer re­moves data from that hash table whenever it re­ceives a reply mes­sage. As a result, we can derive the cur­rent number of out­stand­ing re­quest mes­sages from the size of the mes­sageData hash table. The Loan­Broker­ProxyRe­quest­Con­sumer main­tains a ref­er­ence to the queueStats col­lec­tion stored inside the Loan­Broker­Proxy so that it can add the new data point to this col­lec­tion.

                                                                                                                                                                                                                                            LoanBrokerProxyRequestConsumer 类
                                                                                                                                                                                                                                            公共类 LoanBrokerProxyRequestConsumer :SmartProxyRequestConsumer
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                ArrayList队列统计;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                公共LoanBrokerProxyRequestConsumer(消息队列请求队列,
                                                                                                                                                                                                                                                                                      消息队列服务请求队列,
                                                                                                                                                                                                                                                                                      消息队列服务回复队列,
                                                                                                                                                                                                                                                                                      哈希表消息数据,
                                                                                                                                                                                                                                                                                      ArrayList队列统计):
                                                                                                                                                                                                                                                    基础(请求队列,服务请求队列,服务回复队列,消息数据)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    this.queueStats = queueStats;
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                protected override void ProcessMessage(消息 requestMsg)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    基.ProcessMessage(requestMsg);
                                                                                                                                                                                                                                                    queueStats.Add(messageData.Count);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            public class Loan­Broker­ProxyRe­quest­Con­sumer : Smart­ProxyRe­quest­Con­sumer
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                Ar­rayL­ist queueStats;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                public Loan­Broker­ProxyRe­quest­Con­sumer(Mes­sageQueue re­questQueue,
                                                                                                                                                                                                                                                                                      Mes­sageQueue ser­vice­Re­questQueue,
                                                                                                                                                                                                                                                                                      Mes­sageQueue ser­vice­ReplyQueue,
                                                                                                                                                                                                                                                                                      Hasht­able mes­sageData,
                                                                                                                                                                                                                                                                                      Ar­rayL­ist queueStats) :
                                                                                                                                                                                                                                                    base(re­questQueue, ser­vice­Re­questQueue, ser­vice­ReplyQueue, mes­sageData)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    this.queueStats = queueStats;
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                pro­tec­ted over­ride void Pro­cess­Mes­sage(Mes­sage re­questMsg)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    base.Pro­cess­Mes­sage(re­questMsg);
                                                                                                                                                                                                                                                    queueStats.Add(mes­sageData.Count);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            

                                                                                                                                                                                                                                            当回复消息到达时, LoanBrokerProxyReplyConsumer会收集两个所需的指标。首先,它计算发送请求消息和接收回复消息之间所花费的时间,并将该指标添加到PerformanceStats集合中。其次,它捕获剩余的未完成请求数(再次使用messageData哈希表的大小)并将该数字添加到queueStats集合中。

                                                                                                                                                                                                                                            The Loan­Broker­ProxyReply­Con­sumer col­lects the two re­quired met­rics when a reply mes­sage ar­rives. First, it com­putes the time it took between send­ing the re­quest mes­sage and re­ceiv­ing the reply mes­sage and adds that metric to the per­form­an­ceStats col­lec­tion. Second, it cap­tures the re­main­ing number of out­stand­ing re­quests (again using the size of the mes­sageData hash table) and adds that number to the queueStats col­lec­tion.

                                                                                                                                                                                                                                            LoanBrokerProxyReplyConsumer 类
                                                                                                                                                                                                                                            公共类 LoanBrokerProxyReplyConsumer :SmartProxyReplyConsumer
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                ArrayList队列统计;
                                                                                                                                                                                                                                                ArrayList 性能统计;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                公共LoanBrokerProxyReplyConsumer(消息队列回复队列,
                                                                                                                                                                                                                                                                                    哈希表消息数据,
                                                                                                                                                                                                                                                                                    ArrayList队列统计,
                                                                                                                                                                                                                                                                                    ArrayList 性能统计):
                                                                                                                                                                                                                                                       基础(回复队列,消息数据)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    this.queueStats = queueStats;
                                                                                                                                                                                                                                                    this.performanceStats = PerformanceStats;
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                protected override void AnalyzeMessage(MessageData data, 消息回复消息)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    TimeSpan 持续时间 = DateTime.Now - data.SentTime;
                                                                                                                                                                                                                                                    PerformanceStats.Add(duration.TotalSeconds);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    queueStats.Add(messageData.Count);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            public class Loan­Broker­ProxyReply­Con­sumer : Smart­ProxyReply­Con­sumer
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                Ar­rayL­ist queueStats;
                                                                                                                                                                                                                                                Ar­rayL­ist per­form­an­ceStats;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                public Loan­Broker­ProxyReply­Con­sumer(Mes­sageQueue replyQueue,
                                                                                                                                                                                                                                                                                    Hasht­able mes­sageData,
                                                                                                                                                                                                                                                                                    Ar­rayL­ist queueStats,
                                                                                                                                                                                                                                                                                    Ar­rayL­ist per­form­an­ceStats) :
                                                                                                                                                                                                                                                       base(replyQueue, mes­sageData)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    this.queueStats = queueStats;
                                                                                                                                                                                                                                                    this.per­form­an­ceStats = per­form­an­ceStats;
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                pro­tec­ted over­ride void Ana­lyzeMes­sage(Mes­sageData data, Mes­sage replyMes­sage)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    TimeSpan dur­a­tion = Dat­e­Time.Now - data.Sen­t­Time;
                                                                                                                                                                                                                                                    per­form­an­ceStats.Add(dur­a­tion.TotalSeconds);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    queueStats.Add(mes­sageData.Count);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            

                                                                                                                                                                                                                                            SummaryStats结构根据捕获的数据计算最大值、最小值和平均值。它可以通过从队列大小数据点的数量(为请求和回复消息收集)减去性能数据点的数量(仅为回复消息收集)来得出处理的请求消息的数量。这个结构的实现非常简单,因此我们决定不用代码填充整个页面。

                                                                                                                                                                                                                                            The Sum­maryS­tats struc­ture com­putes max­imum, min­imum, and av­er­age values based on the cap­tured data. It can derive the number of re­quest mes­sages pro­cessed by sub­tract­ing the number of per­form­ance data points (col­lec­ted only for reply mes­sages) from the number of queue size data points (col­lec­ted for re­quest and reply mes­sages). The im­ple­ment­a­tion of this struc­ture is quite trivial, so we de­cided not to fill a whole page with the code.

                                                                                                                                                                                                                                            一旦我们将新的贷款经纪人代理插入消息流中,我们就可以开始收集性能指标。为了收集一些示例数据,我们配置了两个测试客户端,每个客户端发出 50 个贷款报价请求。代理收集了以下结果(我们使用简单的 XSL 转换来渲染以 XML 格式发布到 controlBusQueue 的指标数据的 HTML 表

                                                                                                                                                                                                                                            Once we insert the new loan broker proxy into the mes­sage stream, we can start col­lect­ing per­form­ance met­rics. To col­lect some ex­ample data, we con­figured two test cli­ents to make 50 loan quote re­quests each. The proxy col­lec­ted the fol­low­ing res­ults (we used a simple XSL trans­form to render an HTML table off the metric data pub­lished in XML format to the con­trol­BusQueue):

                                                                                                                                                                                                                                            时间戳

                                                                                                                                                                                                                                            Time Stamp

                                                                                                                                                                                                                                            请求数量

                                                                                                                                                                                                                                            Number of Re­quests

                                                                                                                                                                                                                                            回复数

                                                                                                                                                                                                                                            Number of Replies

                                                                                                                                                                                                                                            最短处理时间

                                                                                                                                                                                                                                            Min­imum Pro­cess­ing Time

                                                                                                                                                                                                                                            平均处理时间

                                                                                                                                                                                                                                            Av­er­age Pro­cess­ing Time

                                                                                                                                                                                                                                            最大处理时间

                                                                                                                                                                                                                                            Max­imum Pro­cess­ing Time

                                                                                                                                                                                                                                            最小队列大小

                                                                                                                                                                                                                                            Min­imum Queue Size

                                                                                                                                                                                                                                            平均队列大小

                                                                                                                                                                                                                                            Av­er­age Queue Size

                                                                                                                                                                                                                                            最大队列大小

                                                                                                                                                                                                                                            Max­imum Queue Size

                                                                                                                                                                                                                                            14:11:02.9644424

                                                                                                                                                                                                                                            14:11:02.9644424

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0.00

                                                                                                                                                                                                                                            0.00

                                                                                                                                                                                                                                            0.00

                                                                                                                                                                                                                                            0.00

                                                                                                                                                                                                                                            0.00

                                                                                                                                                                                                                                            0.00

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            14:11:07.9718424

                                                                                                                                                                                                                                            14:11:07.9718424

                                                                                                                                                                                                                                            89

                                                                                                                                                                                                                                            89

                                                                                                                                                                                                                                            7

                                                                                                                                                                                                                                            7

                                                                                                                                                                                                                                            0.78

                                                                                                                                                                                                                                            0.78

                                                                                                                                                                                                                                            2.54

                                                                                                                                                                                                                                            2.54

                                                                                                                                                                                                                                            3.93

                                                                                                                                                                                                                                            3.93

                                                                                                                                                                                                                                            1

                                                                                                                                                                                                                                            1

                                                                                                                                                                                                                                            42

                                                                                                                                                                                                                                            42

                                                                                                                                                                                                                                            82

                                                                                                                                                                                                                                            82

                                                                                                                                                                                                                                            14:11:12.9792424

                                                                                                                                                                                                                                            14:11:12.9792424

                                                                                                                                                                                                                                            11

                                                                                                                                                                                                                                            11

                                                                                                                                                                                                                                            9

                                                                                                                                                                                                                                            9

                                                                                                                                                                                                                                            4.31

                                                                                                                                                                                                                                            4.31

                                                                                                                                                                                                                                            6.43

                                                                                                                                                                                                                                            6.43

                                                                                                                                                                                                                                            8.69

                                                                                                                                                                                                                                            8.69

                                                                                                                                                                                                                                            83

                                                                                                                                                                                                                                            83

                                                                                                                                                                                                                                            87

                                                                                                                                                                                                                                            87

                                                                                                                                                                                                                                            91

                                                                                                                                                                                                                                            91

                                                                                                                                                                                                                                            14:11:17.9866424

                                                                                                                                                                                                                                            14:11:17.9866424

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            8

                                                                                                                                                                                                                                            8

                                                                                                                                                                                                                                            9.39

                                                                                                                                                                                                                                            9.39

                                                                                                                                                                                                                                            10.83

                                                                                                                                                                                                                                            10.83

                                                                                                                                                                                                                                            12.82

                                                                                                                                                                                                                                            12.82

                                                                                                                                                                                                                                            77

                                                                                                                                                                                                                                            77

                                                                                                                                                                                                                                            80

                                                                                                                                                                                                                                            80

                                                                                                                                                                                                                                            84

                                                                                                                                                                                                                                            84

                                                                                                                                                                                                                                            14:11:22.9940424

                                                                                                                                                                                                                                            14:11:22.9940424

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            8

                                                                                                                                                                                                                                            8

                                                                                                                                                                                                                                            13.80

                                                                                                                                                                                                                                            13.80

                                                                                                                                                                                                                                            15.75

                                                                                                                                                                                                                                            15.75

                                                                                                                                                                                                                                            17.48

                                                                                                                                                                                                                                            17.48

                                                                                                                                                                                                                                            69

                                                                                                                                                                                                                                            69

                                                                                                                                                                                                                                            72

                                                                                                                                                                                                                                            72

                                                                                                                                                                                                                                            76

                                                                                                                                                                                                                                            76

                                                                                                                                                                                                                                            14:11:28.0014424

                                                                                                                                                                                                                                            14:11:28.0014424

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            7

                                                                                                                                                                                                                                            7

                                                                                                                                                                                                                                            18.37

                                                                                                                                                                                                                                            18.37

                                                                                                                                                                                                                                            20.19

                                                                                                                                                                                                                                            20.19

                                                                                                                                                                                                                                            22.18

                                                                                                                                                                                                                                            22.18

                                                                                                                                                                                                                                            62

                                                                                                                                                                                                                                            62

                                                                                                                                                                                                                                            65

                                                                                                                                                                                                                                            65

                                                                                                                                                                                                                                            68

                                                                                                                                                                                                                                            68

                                                                                                                                                                                                                                            14:11:33.0088424

                                                                                                                                                                                                                                            14:11:33.0088424

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            6

                                                                                                                                                                                                                                            6

                                                                                                                                                                                                                                            22.90

                                                                                                                                                                                                                                            22.90

                                                                                                                                                                                                                                            24.83

                                                                                                                                                                                                                                            24.83

                                                                                                                                                                                                                                            26.94

                                                                                                                                                                                                                                            26.94

                                                                                                                                                                                                                                            56

                                                                                                                                                                                                                                            56

                                                                                                                                                                                                                                            58

                                                                                                                                                                                                                                            58

                                                                                                                                                                                                                                            61

                                                                                                                                                                                                                                            61

                                                                                                                                                                                                                                            14:11:38.0162424

                                                                                                                                                                                                                                            14:11:38.0162424

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            10

                                                                                                                                                                                                                                            10

                                                                                                                                                                                                                                            27.74

                                                                                                                                                                                                                                            27.74

                                                                                                                                                                                                                                            29.53

                                                                                                                                                                                                                                            29.53

                                                                                                                                                                                                                                            31.62

                                                                                                                                                                                                                                            31.62

                                                                                                                                                                                                                                            46

                                                                                                                                                                                                                                            46

                                                                                                                                                                                                                                            50

                                                                                                                                                                                                                                            50

                                                                                                                                                                                                                                            55

                                                                                                                                                                                                                                            55

                                                                                                                                                                                                                                            14:11:43.0236424

                                                                                                                                                                                                                                            14:11:43.0236424

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            9

                                                                                                                                                                                                                                            9

                                                                                                                                                                                                                                            31.87

                                                                                                                                                                                                                                            31.87

                                                                                                                                                                                                                                            34.47

                                                                                                                                                                                                                                            34.47

                                                                                                                                                                                                                                            36.30

                                                                                                                                                                                                                                            36.30

                                                                                                                                                                                                                                            37

                                                                                                                                                                                                                                            37

                                                                                                                                                                                                                                            41

                                                                                                                                                                                                                                            41

                                                                                                                                                                                                                                            45

                                                                                                                                                                                                                                            45

                                                                                                                                                                                                                                            14:11:48.0310424

                                                                                                                                                                                                                                            14:11:48.0310424

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            7

                                                                                                                                                                                                                                            7

                                                                                                                                                                                                                                            36.87

                                                                                                                                                                                                                                            36.87

                                                                                                                                                                                                                                            39.06

                                                                                                                                                                                                                                            39.06

                                                                                                                                                                                                                                            40.98

                                                                                                                                                                                                                                            40.98

                                                                                                                                                                                                                                            30

                                                                                                                                                                                                                                            30

                                                                                                                                                                                                                                            33

                                                                                                                                                                                                                                            33

                                                                                                                                                                                                                                            36

                                                                                                                                                                                                                                            36

                                                                                                                                                                                                                                            14:11:53.0384424

                                                                                                                                                                                                                                            14:11:53.0384424

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            9

                                                                                                                                                                                                                                            9

                                                                                                                                                                                                                                            41.75

                                                                                                                                                                                                                                            41.75

                                                                                                                                                                                                                                            43.82

                                                                                                                                                                                                                                            43.82

                                                                                                                                                                                                                                            45.14

                                                                                                                                                                                                                                            45.14

                                                                                                                                                                                                                                            21

                                                                                                                                                                                                                                            21

                                                                                                                                                                                                                                            25

                                                                                                                                                                                                                                            25

                                                                                                                                                                                                                                            29

                                                                                                                                                                                                                                            29

                                                                                                                                                                                                                                            14:11:58.0458424

                                                                                                                                                                                                                                            14:11:58.0458424

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            8

                                                                                                                                                                                                                                            8

                                                                                                                                                                                                                                            45.92

                                                                                                                                                                                                                                            45.92

                                                                                                                                                                                                                                            47.67

                                                                                                                                                                                                                                            47.67

                                                                                                                                                                                                                                            49.67

                                                                                                                                                                                                                                            49.67

                                                                                                                                                                                                                                            13

                                                                                                                                                                                                                                            13

                                                                                                                                                                                                                                            16

                                                                                                                                                                                                                                            16

                                                                                                                                                                                                                                            20

                                                                                                                                                                                                                                            20

                                                                                                                                                                                                                                            14:12:03.0532424

                                                                                                                                                                                                                                            14:12:03.0532424

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            8

                                                                                                                                                                                                                                            8

                                                                                                                                                                                                                                            50.86

                                                                                                                                                                                                                                            50.86

                                                                                                                                                                                                                                            52.58

                                                                                                                                                                                                                                            52.58

                                                                                                                                                                                                                                            54.59

                                                                                                                                                                                                                                            54.59

                                                                                                                                                                                                                                            5

                                                                                                                                                                                                                                            5

                                                                                                                                                                                                                                            8

                                                                                                                                                                                                                                            8

                                                                                                                                                                                                                                            12

                                                                                                                                                                                                                                            12

                                                                                                                                                                                                                                            14:12:08.0606424

                                                                                                                                                                                                                                            14:12:08.0606424

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            4

                                                                                                                                                                                                                                            4

                                                                                                                                                                                                                                            55.41

                                                                                                                                                                                                                                            55.41

                                                                                                                                                                                                                                            55.96

                                                                                                                                                                                                                                            55.96

                                                                                                                                                                                                                                            56.69

                                                                                                                                                                                                                                            56.69

                                                                                                                                                                                                                                            1

                                                                                                                                                                                                                                            1

                                                                                                                                                                                                                                            2

                                                                                                                                                                                                                                            2

                                                                                                                                                                                                                                            4

                                                                                                                                                                                                                                            4

                                                                                                                                                                                                                                            14:12:13.0680424

                                                                                                                                                                                                                                            14:12:13.0680424

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0.00

                                                                                                                                                                                                                                            0.00

                                                                                                                                                                                                                                            0.00

                                                                                                                                                                                                                                            0.00

                                                                                                                                                                                                                                            0.00

                                                                                                                                                                                                                                            0.00

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            0

                                                                                                                                                                                                                                            加载到 Excel 图表中,队列大小数据如下所示:

                                                                                                                                                                                                                                            Loaded into an Excel chart, the queue size data looks like this:

                                                                                                                                                                                                                                            贷款经纪人智能代理统计数据

                                                                                                                                                                                                                                            Loan Broker Smart Proxy Stat­ist­ics

                                                                                                                                                                                                                                            图形/12inf04.gif

                                                                                                                                                                                                                                            我们可以看到,这两个测试客户几乎淹没了贷款经纪人,峰值约为 90 个待处理请求。然后,贷款经纪人以每秒约 2 条请求消息的稳定速率处理请求。由于排队请求数量较多,响应时间很差,峰值接近 1 分钟。好消息是贷款经纪人可以优雅地处理大量突发请求,而坏消息是响应时间非常长。为了缩短响应时间,我们可以执行多个贷款经纪人实例或多个信用局实例(事实证明,信用局服务是第 9 章“插曲:组合消息传递”中描述的原始实现中的瓶颈

                                                                                                                                                                                                                                            We can see that the two test cli­ents pretty much flooded the loan broker, peak­ing at about 90 pending re­quests. The loan broker then pro­cesses the re­quests at a stable rate of about 2 re­quest mes­sages per second. Due to the large number of queued-up re­quests, the re­sponse times are pretty poor, peak­ing at almost 1 minute. The good news is that the loan broker handles a large number of sudden re­quests grace­fully, while the bad news is that the re­sponse times are very long. In order to im­prove re­sponse times, we could ex­ecute mul­tiple loan broker in­stances or mul­tiple credit bureau in­stances (the credit bureau ser­vice turned out to be a bot­tle­neck in the ori­ginal im­ple­ment­a­tion de­scribed in Chapter 9, "In­ter­lude: Com­posed Mes­saging").

                                                                                                                                                                                                                                            验证信用局的运作

                                                                                                                                                                                                                                            Verify the Credit Bureau Op­er­a­tion

                                                                                                                                                                                                                                            管理解决方案的第二个要求是监控外部信用局服务的正确运行。贷款经纪人访问此服务以获得请求贷款报价的客户的信用评分,因为银行需要此信息来提供准确的报价。

                                                                                                                                                                                                                                            The second re­quire­ment for the man­age­ment solu­tion is to mon­itor the cor­rect op­er­a­tion of the ex­ternal credit bureau ser­vice. The loan broker ac­cesses this ser­vice to obtain credit scores for cus­tom­ers re­quest­ing a loan quote be­cause the banks re­quire this in­form­a­tion to provide an ac­cur­ate quote.

                                                                                                                                                                                                                                            为了验证外部信用局服务的正确运行,我们决定定期向该服务发送测试消息。由于信用局服务支持返回地址,因此可以轻松注入测试消息,而不会干扰现有的消息流。我们只是为测试消息提供专用的回复通道,这样就不需要单独的测试消息分隔符(见图)。

                                                                                                                                                                                                                                            In order to verify the cor­rect op­er­a­tion of the ex­ternal credit bureau ser­vice, we decide to send peri­odic Test Mes­sages to the ser­vice. Be­cause the credit bureau ser­vice sup­ports a Return Ad­dress, it is easy to inject a Test Mes­sage without dis­turb­ing the ex­ist­ing mes­sage flow. We simply provide a ded­ic­ated reply chan­nel for test mes­sages which avoids the need for a sep­ar­ate test mes­sage sep­ar­ator (see figure).

                                                                                                                                                                                                                                            监控信用局服务

                                                                                                                                                                                                                                            Mon­it­or­ing the Credit Bureau Ser­vice

                                                                                                                                                                                                                                            图形/12inf05.gif

                                                                                                                                                                                                                                            为了验证征信机构服务的正确运行,我们需要一个测试数据生成器和一个测试数据验证器。测试数据生成器创建要发送到被测服务的测试数据。信用局测试消息非常简单;唯一需要填写的字段是社会安全号码 (SSN)。在我们的测试中,我们使用一个特殊的、固定的 SSN 来识别虚构的人。这使我们能够通过将结果数据与预先建立的结果进行比较来验证结果数据。这样我们不仅可以检查是否收到回复消息,还可以验证消息内容是否正确。在第 9 章介绍的简单示例中,“插曲:组合消息”,信用局服务被编程为返回随机结果,无论传入的 SSN 是什么。因此,我们的测试数据验证程序不会检查特定的结果值,而是验证结果是否在允许的范围内(例如,信用评分为 300 到 900)。如果结果超出允许的范围(例如,由于计算错误将分数设置为零),测试数据验证器会通过消息通知管理控制台。

                                                                                                                                                                                                                                            In order to verify the cor­rect op­er­a­tion of the credit bureau ser­vice, we need a test data gen­er­ator and a test data veri­fier. The test data gen­er­ator cre­ates test data to be sent to the ser­vice under test. A credit bureau test mes­sage is very simple; the only field that is re­quired is a social se­cur­ity number (SSN). For our tests we use a spe­cial, fixed SSN that iden­ti­fies a fic­ti­tious person. This allows us to verify the result data by com­par­ing it to prees­tab­lished res­ults. This way we can not only check whether we re­ceive a reply mes­sage but also verify that the con­tent of the mes­sage is cor­rect. In our simple ex­ample in­tro­duced in Chapter 9, "In­ter­lude: Com­posed Mes­saging," the credit bureau ser­vice is pro­grammed to return random res­ults re­gard­less of the in­com­ing SSN. As a result, our test data veri­fier does not check for spe­cific result values but in­stead veri­fies whether the res­ults are within the al­lowed range (e.g., 300 to 900 for a credit score). If the res­ults fall out­side the al­lowed range (for ex­ample, be­cause a com­pu­ta­tional error set the score to zero), the test data veri­fier no­ti­fies the man­age­ment con­sole with a mes­sage.

                                                                                                                                                                                                                                            测试数据验证器还检查外部服务的响应时间。如果我们在预设的时间间隔内没有收到回复消息,我们也会向管理控制台发出警报。为了最大限度地减少网络带宽,测试数据验证程序仅在响应延迟或格式错误时通知控制台,而不是在服务正常运行时通知控制台。当监视器在检测到错误后从服务收到正确的回复消息时,会发生此规则的唯一例外。在这种情况下,监控器会向管理控制台发送“服务正常”消息,表明信用局再次正常工作。最后,在启动过程中,监视器会向控制台发送一条消息以宣布其存在。

                                                                                                                                                                                                                                            The test data veri­fier also checks the re­sponse time of the ex­ternal ser­vice. If we do not re­ceive a reply mes­sage within a preset time in­ter­val, we also alert the man­age­ment con­sole. To min­im­ize net­work band­width, the test data veri­fier no­ti­fies the con­sole only if the re­sponse is delayed or mal­formed, not when the ser­vice is op­er­at­ing cor­rectly. The only ex­cep­tion to this rule occurs when the mon­itor re­ceives a cor­rect reply mes­sage from the ser­vice sub­se­quent to de­tect­ing an error. In that case, the mon­itor sends a "ser­vice OK" mes­sage to the man­age­ment con­sole to in­dic­ate that the credit bureau is work­ing cor­rectly again. Fi­nally, during star­tup, the mon­itor sends a mes­sage to the con­sole to an­nounce its ex­ist­ence. This mes­sage allows the con­sole to "dis­cover" all active mon­it­ors so it can dis­play the status for each.

                                                                                                                                                                                                                                            监视器实现使用两个单独的计时器:一个用于指定的时间间隔发送测试消息,另一个用于在指定的超时期限内未到达响应时标记异常(见图)。发送定时器确定最后接收消息或最后超时事件与发送下一条测试消息之间的时间间隔。每当监视器发送请求消息时,超时计时器就会启动。如果回复消息在指定的超时间隔内到达,则超时计时器将重置并重新启动下一条请求消息。如果监视器在指定的时间间隔内未收到回复消息,则超时定时器将触发,监视器将向控制总线发送错误消息。然后,它启动一个新的发送计时器,以在发送间隔后发起新的请求消息。现实生活场景可能会使用相对较短的超时(几秒)和较长的发送间隔(例如,一分钟)。

                                                                                                                                                                                                                                            The mon­itor im­ple­ment­a­tion uses two sep­ar­ate timers: one to send Test Mes­sages in spe­cified in­ter­vals and an­other to flag an ex­cep­tion if a re­sponse does not arrive within the spe­cified timeout period (see dia­gram). The Send Timer de­term­ines the time in­ter­val between the last re­ceived mes­sage or the last timeout event and send­ing the next Test Mes­sage. The Timeout Timer is star­ted whenever the mon­itor sends a re­quest mes­sage. If a reply mes­sage ar­rives within the spe­cified timeout in­ter­val, the Timeout Timer is reset and re­star­ted with the next re­quest mes­sage. If the mon­itor does not re­ceive a reply mes­sage within the spe­cified in­ter­val, the Timeout Timer trig­gers and the mon­itor sends an error mes­sage to the con­trol bus. It then starts a new Send Timer to ini­ti­ate a new re­quest mes­sage after the send in­ter­val. A real-life scen­ario is likely to use a re­l­at­ively short timeout (a few seconds) and a longer send in­ter­val (e.g., one minute).

                                                                                                                                                                                                                                            下图说明了两个定时器之间的依赖关系。在这种情况下,监视器发送测试消息并启动超时计时器。在计时器到期之前响应消息到达,因此监视器取消超时计时器并启动间隔计时器。当间隔定时器到期时,监视器发送新的测试消息并启动新的超时定时器。这次,超时计时器在回复消息到达之前到期,导致监视器向控制总线发送消息。同时,显示器启动新的间隔计时器。

                                                                                                                                                                                                                                            The fol­low­ing figure il­lus­trates the de­pend­en­cies between the two timers. In this scen­ario, the mon­itor sends a Test Mes­sage and starts the Timeout Timer. A re­sponse mes­sage ar­rives before the timer elapses, so the mon­itor can­cels the Timeout Timer and starts the In­ter­val Timer. When the In­ter­val Timer elapses, the mon­itor sends a new Test Mes­sage and starts a new Timeout Timer. This time, the Timeout Timer ex­pires before the reply mes­sage ar­rives, caus­ing the mon­itor to send a mes­sage to the Con­trol Bus. At the same time, the mon­itor starts a new In­ter­val Timer.

                                                                                                                                                                                                                                            监控信用局服务

                                                                                                                                                                                                                                            Mon­it­or­ing the Credit Bureau Ser­vice

                                                                                                                                                                                                                                            图形/12inf06.gif

                                                                                                                                                                                                                                            监视器的实现只需要一个类。Monitor类继承自智能代理模式中引入的MessageConsumer。此类配置入站通道并启动事件驱动的使用者来接收消息。对于每个传入消息,它都会调用虚拟ProcessMessage 方法。继承类可以简单地重写此方法来添加自己的处理。

                                                                                                                                                                                                                                            The im­ple­ment­a­tion of the mon­itor re­quires only a single class. The Mon­itor class in­her­its from the Mes­sage­Con­sumer in­tro­duced in the Smart Proxy pat­tern. This class con­fig­ures an in­bound chan­nel and starts an Event-Driven Con­sumer to re­ceive mes­sages. For each in­com­ing mes­sage, it in­vokes the vir­tual Pro­cess­Mes­sage method. An in­her­it­ing class can simply over­ride this method to add its own pro­cess­ing.

                                                                                                                                                                                                                                            Process方法指示MessageConsumer 开始消费消息。Monitor类通过启动发送计时器来增强此方法的基本实现。当此计时器触发时,它会调用OnSendTimerEvent方法。Process方法还将MonitorStatus 类型的消息发送到控制总线以宣布其存在。

                                                                                                                                                                                                                                            The Pro­cess method in­structs a Mes­sage­Con­sumer to start con­sum­ing mes­sages. The Mon­itor class aug­ments the base im­ple­ment­a­tion of this method by start­ing the Send Timer. When this timer trig­gers, it in­vokes the On­Send­TimerEvent method. The Pro­cess method also sends a mes­sage of type Mon­it­or­Status to the Con­trol Bus to an­nounce its ex­ist­ence.

                                                                                                                                                                                                                                            监控类:发送消息
                                                                                                                                                                                                                                            公共重写 void Process()
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                基.进程();
                                                                                                                                                                                                                                                sendTimer = 新计时器(新 TimerCallback
                                                                                                                                                                                                                                                                     (OnSendTimerEvent), null, 间隔*1000, Timeout.Infinite);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                MonitorStatus 状态 = new MonitorStatus(
                                                                                                                                                                                                                                                             MonitorStatus.STATUS_ANNOUNCE, "监控在线", null, MonitorID);
                                                                                                                                                                                                                                                Console.WriteLine(状态.描述);
                                                                                                                                                                                                                                                controlQueue.Send(状态);
                                                                                                                                                                                                                                                最后状态 = 状态.Status;
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            protected void OnSendTimerEvent(对象状态)
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                CreditBureauRequest 请求 = new CreditBureauRequest();
                                                                                                                                                                                                                                                请求.SSN = SSN;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                消息 requestMessage = new Message(请求);
                                                                                                                                                                                                                                                requestMessage.Priority = MessagePriority.AboveNormal;
                                                                                                                                                                                                                                                requestMessage.ResponseQueue = inputQueue;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                Console.WriteLine(DateTime.Now.ToString() + "发送请求消息");
                                                                                                                                                                                                                                                requestQueue.Send(requestMessage);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                correlationID = requestMessage.Id;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                timeoutTimer = new Timer(new TimerCallback(OnTimeoutEvent), null,
                                                                                                                                                                                                                                                                         超时*1000,超时.无限);
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            public over­ride void Pro­cess()
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                base.Pro­cess();
                                                                                                                                                                                                                                                send­Timer = new Timer(new Timer­Call­back
                                                                                                                                                                                                                                                                     (On­Send­TimerEvent), null, in­ter­val*1000, Timeout.In­fin­ite);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                Mon­it­or­Status status = new Mon­it­or­Status(
                                                                                                                                                                                                                                                             Mon­it­or­Status.STATUS_AN­NOUNCE, "Mon­itor On-Line", null, Mon­it­orID);
                                                                                                                                                                                                                                                Con­sole.WriteLine(status.De­scrip­tion);
                                                                                                                                                                                                                                                con­trolQueue.Send(status);
                                                                                                                                                                                                                                                last­Status = status.Status;
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            pro­tec­ted void On­Send­TimerEvent(Object state)
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                Cred­it­Bur­eauRe­quest re­quest = new Cred­it­Bur­eauRe­quest();
                                                                                                                                                                                                                                                re­quest.SSN = SSN;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                Mes­sage re­quest­Mes­sage = new Mes­sage(re­quest);
                                                                                                                                                                                                                                                re­quest­Mes­sage.Pri­or­ity = Mes­sage­Pri­or­ity.AboveNor­mal;
                                                                                                                                                                                                                                                re­quest­Mes­sage.Re­spon­se­Queue = in­putQueue;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                Con­sole.WriteLine(Dat­e­Time.Now.To­String() + " Send­ing re­quest mes­sage");
                                                                                                                                                                                                                                                re­questQueue.Send(re­quest­Mes­sage);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                cor­rel­a­tionID = re­quest­Mes­sage.Id;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                timeout­Timer = new Timer(new Timer­Call­back(On­TimeoutEvent), null,
                                                                                                                                                                                                                                                                         timeout*1000, Timeout.In­fin­ite);
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            

                                                                                                                                                                                                                                            OnSendTimerEvent方法创建一条新的请求消息。请求消息中的唯一参数是客户的 SSN。该方法指定固定的 SSN。该方法还保存消息 ID 以验证任何传入回复消息的相关标识符。最后,它启动timeoutTimer,以便在设定的时间间隔后如果没有收到回复消息,则通知监视器。

                                                                                                                                                                                                                                            The On­Send­TimerEvent method cre­ates a new re­quest mes­sage. The only para­meter in the re­quest mes­sage is the cus­tomer's SSN. The method spe­cifies a fixed SSN. The method also saves the mes­sage ID to verify the Cor­rel­a­tion Iden­ti­fier of any in­com­ing reply mes­sages. Lastly, it starts the timeout­Timer so that the mon­itor is being no­ti­fied after a set time in­ter­val if no reply mes­sage is re­ceived.

                                                                                                                                                                                                                                            该方法将测试消息的Priority属性设置为BelowNormal,以确保排队的应用程序消息不会让服务看起来好像不可用。对测试消息使用较高的优先级会导致消息队列在排队的应用程序消息之前传送这些消息。在这种情况下,设置较高的消息优先级是安全的,因为测试数据生成器会注入非常少量的测试消息。如果我们将大量高优先级消息注入请求通道,则可能会中断应用程序消息流。这肯定会违反管理解决方案尽可能减少侵入的意图。

                                                                                                                                                                                                                                            The method sets the test mes­sage's Pri­or­ity prop­erty to AboveNor­mal to make sure that queued-up ap­plic­a­tion mes­sages do not let the ser­vice appear as if it is not avail­able. Using a higher pri­or­ity for Test Mes­sages causes the mes­sage queue to de­liver these mes­sages ahead of queued-up ap­plic­a­tion mes­sages. Set­ting a higher mes­sage pri­or­ity is safe in this case be­cause the test data gen­er­ator in­jects a very small volume of Test Mes­sages. If we in­jec­ted a large volume of high-pri­or­ity mes­sages into the re­quest chan­nel, we could in­ter­rupt the flow of ap­plic­a­tion mes­sages. This would def­in­itely vi­ol­ate the in­ten­tion of a man­age­ment solu­tion to be as min­im­ally in­trus­ive as pos­sible.

                                                                                                                                                                                                                                            ProcessMessage方法Monitor。它实现测试消息验证器,评估传入的回复消息。停止超时计时器后,该方法检查传入消息的正确相关标识符、消息正文的正确数据类型以及消息正文中的合理值。如果这些测试中的任何一个失败,该方法都会设置一个MonitorStatus结构并将其发送到控制总线通道。监视器还跟踪存储在lastStatus变量中的先前状态。如果状态从“错误”更改为“正常”,则ProcessMessage方法还向控制总线发送通知。

                                                                                                                                                                                                                                            The Pro­cess­Mes­sage method is the heart of the Mon­itor class. It im­ple­ments the test mes­sage veri­fier, eval­u­at­ing in­com­ing reply mes­sages. After stop­ping the timeout timer, the method checks the in­com­ing mes­sage for the cor­rect Cor­rel­a­tion Iden­ti­fier, cor­rect data­type of the mes­sage body, and reas­on­able values inside the mes­sage body. If any of these tests fail, the method sets up a Mon­it­or­Status struc­ture and sends it to the Con­trol Bus chan­nel. The mon­itor also tracks the pre­vi­ous status that is stored in the last­Status vari­able. If the status changes from "error" to "OK," the Pro­cess­Mes­sage method also sends a no­ti­fic­a­tion to the Con­trol Bus.

                                                                                                                                                                                                                                            监听类:接收消息
                                                                                                                                                                                                                                            受保护的覆盖无效 ProcessMessage(消息 msg)
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                Console.WriteLine(DateTime.Now.ToString() + "收到回复消息");
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                if (超时定时器!= null)
                                                                                                                                                                                                                                                    超时定时器.Dispose();
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                msg.Formatter = new XmlMessageFormatter(new Type[] {typeof(CreditBureauReply)});
                                                                                                                                                                                                                                                CreditBureau回复replyStruct;
                                                                                                                                                                                                                                                MonitorStatus 状态 = new MonitorStatus();
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                状态.Status = MonitorStatus.STATUS_OK;
                                                                                                                                                                                                                                                status.Description = "无错误";
                                                                                                                                                                                                                                                状态.ID = 监视器ID;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                尝试
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    if (msg.Body 是 CreditBureauReply)
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        replyStruct = (CreditBureauReply)msg.Body;
                                                                                                                                                                                                                                                        if (msg.CorrelationId != correlationID)
                                                                                                                                                                                                                                                        {
                                                                                                                                                                                                                                                            状态.Status = MonitorStatus.STATUS_FAILED_CORRELATION;
                                                                                                                                                                                                                                                            状态.描述 =
                                                                                                                                                                                                                                                                “传入消息相关 ID 与传出消息 ID 不匹配”;
                                                                                                                                                                                                                                                        }
                                                                                                                                                                                                                                                        别的
                                                                                                                                                                                                                                                        {
                                                                                                                                                                                                                                                            if (replyStruct.CreditScore < 300 ||replyStruct.CreditScore > 900 ||
                                                                                                                                                                                                                                                                replyStruct.HistoryLength < 1 || replyStruct.HistoryLength > 24)
                                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                                状态.Status = MonitorStatus.STATUS_INVALID_DATA;
                                                                                                                                                                                                                                                                status.Description = "信用评分值超出范围";
                                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                                        }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                    别的
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        状态.Status = MonitorStatus.STATUS_INVALID_FORMAT;
                                                                                                                                                                                                                                                        status.Description = "消息格式无效"; }
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                                捕获(异常 e)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    Console.WriteLine("异常:{0}", e.ToString());
                                                                                                                                                                                                                                                    状态.Status = MonitorStatus.STATUS_INVALID_FORMAT;
                                                                                                                                                                                                                                                    status.Description = "无法反序列化消息正文";
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                StreamReader reader = new StreamReader(msg.BodyStream);
                                                                                                                                                                                                                                                status.MessageBody = reader.ReadToEnd();
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                Console.WriteLine(状态.描述);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                if (status.Status != MonitorStatus.STATUS_OK ||
                                                                                                                                                                                                                                                    (status.Status == MonitorStatus.STATUS_OK &&
                                                                                                                                                                                                                                                     最后状态!= MonitorStatus.STATUS_OK))
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    controlQueue.Send(状态);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                最后状态 = 状态.Status;
                                                                                                                                                                                                                                                sendTimer.Dispose();
                                                                                                                                                                                                                                                sendTimer = new Timer(new TimerCallback(OnSendTimerEvent), null,
                                                                                                                                                                                                                                                                      间隔*1000,超时.无限);
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            pro­tec­ted over­ride void Pro­cess­Mes­sage(Mes­sage msg)
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                Con­sole.WriteLine(Dat­e­Time.Now.To­String() + " Re­ceived reply mes­sage");
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                if (timeout­Timer != null)
                                                                                                                                                                                                                                                    timeout­Timer.Dis­pose();
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                msg.Format­ter = new Xm­lMes­sage­Format­ter(new Type[] {typeof(Cred­it­Bur­eauReply)});
                                                                                                                                                                                                                                                Cred­it­Bur­eauReply reply­S­truct;
                                                                                                                                                                                                                                                Mon­it­or­Status status = new Mon­it­or­Status();
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                status.Status = Mon­it­or­Status.STATUS_OK;
                                                                                                                                                                                                                                                status.De­scrip­tion = "No Error";
                                                                                                                                                                                                                                                status.ID = Mon­it­orID;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                try
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    if (msg.Body is Cred­it­Bur­eauReply)
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        reply­S­truct = (Cred­it­Bur­eauReply)msg.Body;
                                                                                                                                                                                                                                                        if (msg.Cor­rel­a­tionId != cor­rel­a­tionID)
                                                                                                                                                                                                                                                        {
                                                                                                                                                                                                                                                            status.Status = Mon­it­or­Status.STATUS_­FAILED_­COR­REL­A­TION;
                                                                                                                                                                                                                                                            status.De­scrip­tion =
                                                                                                                                                                                                                                                                "In­com­ing mes­sage cor­rel­a­tion ID does not match out­go­ing mes­sage ID";
                                                                                                                                                                                                                                                        }
                                                                                                                                                                                                                                                        else
                                                                                                                                                                                                                                                        {
                                                                                                                                                                                                                                                            if (reply­S­truct.Cred­itScore < 300 || reply­S­truct.Cred­itScore > 900 ||
                                                                                                                                                                                                                                                                reply­S­truct.His­toryLength < 1 || reply­S­truct.His­toryLength > 24)
                                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                                status.Status = Mon­it­or­Status.STATUS_IN­VAL­ID_DATA;
                                                                                                                                                                                                                                                                status.De­scrip­tion = "Credit score values out of range";
                                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                                        }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                    else
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        status.Status = Mon­it­or­Status.STATUS_IN­VAL­ID_­FORMAT;
                                                                                                                                                                                                                                                        status.De­scrip­tion = "In­valid mes­sage format";        }
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                                catch (Ex­cep­tion e)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    Con­sole.WriteLine("Ex­cep­tion: {0}", e.To­String());
                                                                                                                                                                                                                                                    status.Status = Mon­it­or­Status.STATUS_IN­VAL­ID_­FORMAT;
                                                                                                                                                                                                                                                    status.De­scrip­tion = "Could not deseri­al­ize mes­sage body";
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                Stream­Reader reader = new Stream­Reader (msg.BodyS­tream);
                                                                                                                                                                                                                                                status.Mes­sage­Body =  reader.Read­ToEnd();
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                Con­sole.WriteLine(status.De­scrip­tion);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                if (status.Status != Mon­it­or­Status.STATUS_OK ||
                                                                                                                                                                                                                                                    (status.Status == Mon­it­or­Status.STATUS_OK &&
                                                                                                                                                                                                                                                     last­Status != Mon­it­or­Status.STATUS_OK))
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    con­trolQueue.Send(status);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                last­Status = status.Status;
                                                                                                                                                                                                                                                send­Timer.Dis­pose();
                                                                                                                                                                                                                                                send­Timer = new Timer(new Timer­Call­back(On­Send­TimerEvent), null,
                                                                                                                                                                                                                                                                      in­ter­val*1000, Timeout.In­fin­ite);
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            

                                                                                                                                                                                                                                            如果在指定的时间间隔内没有消息到达,timeoutTimer将调用OnTimeoutEvent方法。该方法向控制总线发送一条MonitorStatus消息,并启动一个新的发送定时器,以便在该时间间隔后发送新的请求消息。

                                                                                                                                                                                                                                            If no mes­sage ar­rives in the spe­cified in­ter­val, the timeout­Timer will invoke the On­TimeoutEvent method. This method sends a Mon­it­or­Status mes­sage to the Con­trol Bus and starts a new Send Timer so that a new re­quest mes­sage is sent after the in­ter­val.

                                                                                                                                                                                                                                            监控类别:超时
                                                                                                                                                                                                                                            protected void OnTimeoutEvent(对象状态)
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                MonitorStatus 状态 = new MonitorStatus(
                                                                                                                                                                                                                                                                       MonitorStatus.STATUS_TIMEOUT, "超时", null, MonitorID);
                                                                                                                                                                                                                                                Console.WriteLine(状态.描述);
                                                                                                                                                                                                                                                controlQueue.Send(状态);
                                                                                                                                                                                                                                                最后状态 = 状态.Status;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                超时定时器.Dispose();
                                                                                                                                                                                                                                                sendTimer = new Timer(new TimerCallback(OnSendTimerEvent), null,
                                                                                                                                                                                                                                                                      间隔*1000,超时.无限);
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            pro­tec­ted void On­TimeoutEvent(Object state)
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                Mon­it­or­Status status = new Mon­it­or­Status(
                                                                                                                                                                                                                                                                       Mon­it­or­Status.STATUS_­TIMEOUT, "Timeout", null, Mon­it­orID);
                                                                                                                                                                                                                                                Con­sole.WriteLine(status.De­scrip­tion);
                                                                                                                                                                                                                                                con­trolQueue.Send(status);
                                                                                                                                                                                                                                                last­Status = status.Status;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                timeout­Timer.Dis­pose();
                                                                                                                                                                                                                                                send­Timer = new Timer(new Timer­Call­back(On­Send­TimerEvent), null,
                                                                                                                                                                                                                                                                      in­ter­val*1000, Timeout.In­fin­ite);
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            

                                                                                                                                                                                                                                            征信局故障转移

                                                                                                                                                                                                                                            Credit Bureau Fail­over

                                                                                                                                                                                                                                            现在我们可以监控外部信用局服务的状态,我们希望使用这些数据来实施故障转移方案,以便即使信用局服务出现故障,贷款经纪人也可以继续运行。值得注意的是,点对点通道已经提供了基本形式的故障转移。当我们在单个点对点通道上使用多个竞争消费者时,只要其他消费者仍在运行,一个消费者的故障就不会中断处理。当多个消费者处于活动状态时,它们会分割负载,有效地实现简单的负载平衡机制。那么为什么我们需要实施显式的故障转移机制呢?使用外部服务时,我们可能仅限于不支持竞争消费者的简单渠道,例如基于 HTTP 的 SOAP。此外,我们可能不希望多个服务进行负载平衡。例如,我们可能与主要服务提供商签订了批量协议,如果我们满足某些使用配额,该协议将为我们提供大幅折扣。将流量分散到两个提供商可能会花费更多。或者,我们可能使用低成本提供商作为我们的主要服务提供商,并且仅在低成本提供商失败时才希望切换到优质提供商。(有关由许可考虑因素驱动的架构决策的精彩讨论,请参阅 [ Hohmann ]。)

                                                                                                                                                                                                                                            Now that we can mon­itor the status of the ex­ternal credit bureau ser­vice, we want to use this data to im­ple­ment a fail­over scheme so that the loan broker can con­tinue op­er­at­ing even when the credit bureau ser­vice fails. It is worth­while noting that Point-to-Point Chan­nels already provide a basic form of fail­over. When we use mul­tiple Com­pet­ing Con­sumers on a single Point-to-Point Chan­nel, the fail­ure of one con­sumer will not in­ter­rupt pro­cess­ing as long as the other con­sumer(s) still op­er­ate. When mul­tiple con­sumers are active, they split the load, ef­fect­ively im­ple­ment­ing a simple load-bal­an­cing mech­an­ism. Why then would we need to im­ple­ment an ex­pli­cit fail­over mech­an­ism? When using ex­ternal ser­vices, we may be lim­ited to simple chan­nels that do not sup­port Com­pet­ing Con­sumers, such as SOAP over HTTP. Also, we may not want mul­tiple ser­vices to load bal­ance. For ex­ample, we may have a volume agree­ment with the primary ser­vice pro­vider that gives us sub­stan­tial dis­counts if we meet cer­tain usage quotas. Split­ting the traffic across two pro­viders will likely cost us more. Al­tern­at­ively, we may be using a low-cost pro­vider as our primary ser­vice pro­vider and want to switch over to a premium pro­vider only when the low-cost pro­vider fails. (For an ex­cel­lent dis­cus­sion of ar­chi­tec­tural de­cisions driven by li­cens­ing con­sid­er­a­tions, see [Hohmann].)

                                                                                                                                                                                                                                            为了实现显式故障转移,我们将消息路由器插入信用局请求通道(见图)。该路由器将请求路由到主要信用局服务(粗、黑色箭头)或辅助信用局服务(细、黑色箭头)。由于辅助服务可能使用与第一个服务不同的消息格式,因此我们用一对消息转换器包装辅助服务。消息路由器是基于上下文的消息路由器,由管理控制台通过控制总线控制。管理控制台从我们在上一节中设计的信用局监视器获取监控数据。如果监视器指示出现故障,管理控制台会指示消息路由器将流量重新路由到辅助服务提供商(见图)。

                                                                                                                                                                                                                                            In order to im­ple­ment ex­pli­cit fail­over, we insert a Mes­sage Router into the credit bureau re­quest chan­nel (see figure). This router routes the re­quest either to the primary credit bureau ser­vice (thick, black arrows) or to the sec­ond­ary credit bureau ser­vice (thin, black arrows). Be­cause the sec­ond­ary ser­vice may use a dif­fer­ent mes­sage format than the first ser­vice, we wrap the sec­ond­ary ser­vice with a pair of Mes­sage Trans­lat­ors. The Mes­sage Router is a con­text-based Mes­sage Router con­trolled by the man­age­ment con­sole over the Con­trol Bus. The man­age­ment con­sole gets mon­it­or­ing data from the credit bureau mon­itor we de­signed in the pre­vi­ous sec­tion. If the mon­itor in­dic­ates a fail­ure, the man­age­ment con­sole in­structs the Mes­sage Router to reroute the traffic to the sec­ond­ary ser­vice pro­vider (see figure).

                                                                                                                                                                                                                                            使用基于上下文的消息路由器进行显式故障转移

                                                                                                                                                                                                                                            Ex­pli­cit Fail­over with a Con­text-Based Mes­sage Router

                                                                                                                                                                                                                                            图形/12inf07.gif

                                                                                                                                                                                                                                            当请求消息流量重新路由到辅助服务提供商时,监控器继续向主要提供商发送测试消息。当监视器确认服务操作正确时,控制台指示消息路由器返回将请求消息路由到主要提供者。该解决方案图没有显示辅助服务提供商的监视器,尽管使用信用局监视器的第二个实例来监视备份信用局服务的运行状况非常容易。

                                                                                                                                                                                                                                            While the re­quest mes­sage traffic is rerouted to the sec­ond­ary ser­vice pro­vider, the mon­itor keeps on send­ing test mes­sages to the primary pro­vider. When the mon­itor con­firms the cor­rect op­er­a­tion of the ser­vice, the con­sole in­structs the Mes­sage Router to return to rout­ing re­quest mes­sages to the primary pro­vider. The solu­tion dia­gram does not show a mon­itor for the sec­ond­ary ser­vice pro­vider even though it would be very easy to use a second in­stance of the credit bureau mon­itor to mon­itor the health of the backup credit bureau ser­vice.

                                                                                                                                                                                                                                            我们来看一下基于上下文的消息路由器的实现。ContextBasedRouter继承自我们值得信赖的MessageConsumer基类来处理传入消息。ProcessMessage方法检查变量控件,并根据变量的值将传入消息路由到主要或辅助输出通道。

                                                                                                                                                                                                                                            Let's look at the im­ple­ment­a­tion of the con­text-based Mes­sage Router. The Con­text­Based­Router class in­her­its from our trusty Mes­sage­Con­sumer base class to pro­cess in­com­ing mes­sages. The Pro­cess­Mes­sage method checks the value of the vari­able con­trol and routes in­com­ing mes­sages to either the primary or sec­ond­ary output chan­nel based on the value of the vari­able.

                                                                                                                                                                                                                                            ContextBaseRouter 类
                                                                                                                                                                                                                                            委托无效 ControlEvent(int control);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            ContextBasedRouter 类:MessageConsumer
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                            ...
                                                                                                                                                                                                                                                受保护的覆盖无效 ProcessMessage(消息 msg)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    如果(控制== 0)
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        PrimaryOutputQueue.Send(msg);
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                    别的
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        secondaryOutputQueue.Send(msg);
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                受保护的无效OnControlEvent(int控制)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    this.control = 控制;
                                                                                                                                                                                                                                                    Console.WriteLine("Control = " + control);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            del­eg­ate void Con­tro­lEvent(int con­trol);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            class Con­text­Based­Router : Mes­sage­Con­sumer
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                            ...
                                                                                                                                                                                                                                                pro­tec­ted over­ride void Pro­cess­Mes­sage(Mes­sage msg)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    if (con­trol == 0)
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        prima­ry­Out­putQueue.Send(msg);
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                    else
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        sec­ond­a­ry­Out­putQueue.Send(msg);
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                pro­tec­ted void On­Con­tro­lEvent(int con­trol)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    this.con­trol = con­trol;
                                                                                                                                                                                                                                                    Con­sole.WriteLine("Con­trol = " + con­trol);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            

                                                                                                                                                                                                                                            变量控件由OnControlEvent 方法设置。此方法由ControlReceiver类调用,该类也继承自MessageConsumer,因为它侦听来自控制通道的消息。ContextBasedRouter类为ControlReceiver提供ControlEvent类型的委托,以便在收到带有数值的控件事件时调用。如果您还没有遇到过委托,那么它们是一种非常简洁、类型安全的实现回调的方法,而无需实现另一个接口或降级为函数指针([Box]涉及所有血淋淋的细节)。

                                                                                                                                                                                                                                            The vari­able con­trol is set by the On­Con­tro­lEvent method. This method is in­voked by the Con­trolRe­ceiver class, which also in­her­its from Mes­sage­Con­sumer as it listens for mes­sages from the con­trol chan­nel. The Con­text­Based­Router class sup­plies the Con­trolRe­ceiver with a del­eg­ate of type Con­tro­lEvent to invoke when it re­ceives a con­trol event with a nu­meric value. If you have not come across del­eg­ates, they are a really neat, type-safe way to im­ple­ment call­backs without having to im­ple­ment an­other in­ter­face or re­leg­at­ing to func­tion point­ers ([Box] goes into all the gory de­tails).

                                                                                                                                                                                                                                            控制接收器类
                                                                                                                                                                                                                                            类 ControlReceiver : MessageConsumer
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                受保护的 ControlEvent 控制事件;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                公共ControlReceiver(消息队列输入队列,
                                                                                                                                                                                                                                                                       ControlEvent controlEvent) : 基(inputQueue)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    this.controlEvent = controlEvent;
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                受保护的覆盖无效 ProcessMessage(消息 msg)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    字符串文本 = (字符串)msg.Body;
                                                                                                                                                                                                                                                    双倍resNum;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    if (Double.TryParse(文本,NumberStyles.Integer,
                                                                                                                                                                                                                                                                        NumberFormatInfo.InvariantInfo,输出 resNum))
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        int 控制 = int.Parse(text);
                                                                                                                                                                                                                                                        控制事件(控制);
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            class Con­trolRe­ceiver : Mes­sage­Con­sumer
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                pro­tec­ted Con­tro­lEvent con­tro­lEvent;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                public Con­trolRe­ceiver(Mes­sageQueue in­putQueue,
                                                                                                                                                                                                                                                                       Con­tro­lEvent con­tro­lEvent) : base (in­putQueue)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    this.con­tro­lEvent = con­tro­lEvent;
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                pro­tec­ted over­ride void Pro­cess­Mes­sage(Mes­sage msg)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    String text = (string)msg.Body;
                                                                                                                                                                                                                                                    Double resNum;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    if (Double.Try­P­arse(text, Num­ber­Styles.In­teger,
                                                                                                                                                                                                                                                                        Num­ber­Form­at­Info.In­vari­an­tI­nfo, out resNum))
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        int con­trol = int.Parse(text);
                                                                                                                                                                                                                                                        con­tro­lEvent(con­trol);
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            

                                                                                                                                                                                                                                            增强管理控制台

                                                                                                                                                                                                                                            En­han­cing the Man­age­ment Con­sole

                                                                                                                                                                                                                                            管理控制台的第一个版本非常简单,我们甚至懒得展示代码。它所能做的就是接收消息并将消息内容写入文件以供以后分析(例如从 Excel 渲染性能图表)。现在我们想为管理控制台注入更多智能。首先,当主信用局监视器指示故障时,管理控制台需要指示基于上下文的消息路由器将消息重新路由到辅助服务提供商。我们选择在管理控制台内部实现此功能,以便我们可以有效地解耦监视器和基于上下文的消息路由器,管理控制台充当中介者[ GoF ]。此外,在中央位置实施故障转移逻辑为我们提供了系统管理规则的单点维护。商业管理控制台通常包括可配置的规则引擎,用于根据控制总线上的事件确定适当的纠正措施。

                                                                                                                                                                                                                                            The first ver­sion of the man­age­ment con­sole was so simple that we did not even bother show­ing the code. All it could do was re­ceive a mes­sage and write the mes­sage con­tent to a file for later ana­lysis (such as ren­der­ing per­form­ance graphs from Excel). Now we want to inject some more in­tel­li­gence into the man­age­ment con­sole. First, when the primary credit bureau mon­itor in­dic­ates a fail­ure, the man­age­ment con­sole needs to in­struct the con­text-based Mes­sage Router to reroute mes­sages to the sec­ond­ary ser­vice pro­vider. We opted to im­ple­ment this func­tion­al­ity inside the man­age­ment con­sole so that we can de­couple the mon­itor and the con­text-based Mes­sage Router ef­fect­ively, the man­age­ment con­sole acts as a Me­di­ator [GoF]. Also, im­ple­ment­ing the fail­over logic in a cent­ral loc­a­tion gives us a single point of main­ten­ance for the system man­age­ment rules. Com­mer­cial man­age­ment con­soles typ­ic­ally in­clude con­fig­ur­able rules en­gines to de­term­ine ap­pro­pri­ate cor­rect­ive ac­tions based on events on the Con­trol Bus.

                                                                                                                                                                                                                                            其次,我们希望为管理控制台构建一个简单的用户界面,显示系统的当前状态。获得消息传递系统的全局视图可能相当困难,尤其是在消息路径动态变化的情况下。即使组件数量很少,也可能导致协调消息流向变得困难。我们的用户界面很简单,但仍然非常有用。我们使用本书中定义的标志性语言来表示组件之间的交互。目前,用户界面仅显示系统的信用局故障转移部分,由两项服务和一个基于上下文的消息路由器组成;参见下页图。

                                                                                                                                                                                                                                            Second, we want to build a simple user in­ter­face for the man­age­ment con­sole that dis­plays the cur­rent state of the system. Ob­tain­ing a big-pic­ture view of a mes­saging system can be quite dif­fi­cult, es­pe­cially if mes­sage paths change dy­nam­ic­ally. Even a small number of com­pon­ents can make it dif­fi­cult to re­con­cile where mes­sages flow. Our user in­ter­face is simple but nev­er­the­less quite useful. We use the iconic lan­guage defined in this book to rep­res­ent the in­ter­ac­tion between com­pon­ents. For now, the user in­ter­face dis­plays only the credit bureau fail­over por­tion of the system, con­sist­ing of two ser­vices and one con­text-based Mes­sage Router; see figure on the fol­low­ing page.

                                                                                                                                                                                                                                            管理控制台指示两个信用局服务均处于活动状态

                                                                                                                                                                                                                                            The Man­age­ment Con­sole In­dic­ates That Both Credit Bureau Ser­vices Are Active

                                                                                                                                                                                                                                            图形/12inf08.gif

                                                                                                                                                                                                                                            当监视器检测到故障并指示路由器重新路由流量时,我们希望更新用户界面以反映新状态(见图)。路由器图标显示请求消息的新路由,主信用局组件会改变颜色以指示失败。

                                                                                                                                                                                                                                            When the Mon­itor de­tects a fail­ure and in­structs the router to reroute the traffic, we want to update the user in­ter­face to re­flect the new status (see figure). The router icon shows the new route for the re­quest mes­sages, and the primary credit bureau com­pon­ent changes colors to in­dic­ate the fail­ure.

                                                                                                                                                                                                                                            管理控制台指示主信用局发生故障且流量正在重新路由

                                                                                                                                                                                                                                            The Man­age­ment Con­sole In­dic­ates That the Primary Credit Bureau Failed and Traffic Is Being Rerouted

                                                                                                                                                                                                                                            图形/12inf09.gif

                                                                                                                                                                                                                                            让我们简单看一下这个控制台背后的代码。我们专注于代码的系统管理部分,而不深入研究呈现漂亮的用户界面图片的代码的细节。首先,管理控制台需要能够从监视器组件检索状态消息。为了使控制台尽可能健壮,我们以松散耦合的方式访问消息内容,从 XML 有效负载中读取各个字段。即使各个组件决定向消息格式添加新字段,此方法也有助于保持管理控制台的运行。

                                                                                                                                                                                                                                            Let's have a brief look at the code behind this con­sole. We focus on the system man­age­ment part of the code and do not dive into the de­tails of the code that renders the pretty user in­ter­face pic­tures. First, the man­age­ment con­sole needs to be able to re­trieve status mes­sages from the mon­itor com­pon­ent. To make the con­sole as robust as pos­sible, we access the mes­sage con­tent in a loosely coupled fash­ion, read­ing in­di­vidual fields from the XML pay­load. This ap­proach helps keep the man­age­ment con­sole op­er­a­tional even if the in­di­vidual com­pon­ents decide to add new fields to the mes­sage format.

                                                                                                                                                                                                                                            毫不奇怪,控制台类也继承自我们的好朋友MessageConsumer ,因此我们只展示构造函数和ProcessMessage 方法的实现。该方法只是将消息的BodyStream 读取到字符串变量中,并将其传递给不同的组件进行分析。

                                                                                                                                                                                                                                            Not sur­pris­ingly, the con­sole class also in­her­its from our good friend Mes­sage­Con­sumer, so we only show the im­ple­ment­a­tion of the con­structor and the Pro­cess­Mes­sage method. The method simply reads the mes­sage's BodyS­tream into a string vari­able and passes it to the dif­fer­ent com­pon­ents for ana­lysis.

                                                                                                                                                                                                                                            管理控制台:ProcessMessage
                                                                                                                                                                                                                                            公共委托 void ControlMessageReceived(String body);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            公共类管理控制台:MessageConsumer
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                受保护的 Logger 记录器;
                                                                                                                                                                                                                                                公共 MonitorStatusHandler 监视器状态处理程序;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                公共ControlMessage接收更新事件;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                公共管理控制台(消息队列输入队列,字符串路径名称):基(输入队列)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    logger = new Logger(路径名);
                                                                                                                                                                                                                                                    MonitorStatusHandler = new MonitorStatusHandler();
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    updateEvent += new ControlMessageReceived(logger.Log);
                                                                                                                                                                                                                                                    updateEvent += new ControlMessageReceived(monitorStatusHandler.OnControlMessage);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                protected override void ProcessMessage(消息 m)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    流 stm = m.BodyStream;
                                                                                                                                                                                                                                                    StreamReader 阅读器 = new StreamReader (stm);
                                                                                                                                                                                                                                                    字符串主体 = reader.ReadToEnd();
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    更新事件(主体);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                                ...
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            public del­eg­ate void Con­trolMes­sageRe­ceived(String body);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            public class Man­age­ment­Con­sole : Mes­sage­Con­sumer
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                pro­tec­ted Logger logger;
                                                                                                                                                                                                                                                public Mon­it­or­StatusH­and­ler mon­it­or­StatusH­and­ler;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                public Con­trolMes­sageRe­ceived up­dateEvent;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                public Man­age­ment­Con­sole(Mes­sageQueue in­putQueue, string path­Name) : base(in­putQueue)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    logger = new Logger(path­Name);
                                                                                                                                                                                                                                                    mon­it­or­StatusH­and­ler = new Mon­it­or­StatusH­and­ler();
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    up­dateEvent += new Con­trolMes­sageRe­ceived(logger.Log);
                                                                                                                                                                                                                                                    up­dateEvent += new Con­trolMes­sageRe­ceived(mon­it­or­StatusH­and­ler.On­Con­trolMes­sage);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                pro­tec­ted over­ride void Pro­cess­Mes­sage(Mes­sage m)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    Stream stm = m.BodyS­tream;
                                                                                                                                                                                                                                                    Stream­Reader reader = new Stream­Reader (stm);
                                                                                                                                                                                                                                                    String body =  reader.Read­ToEnd();
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    up­dateEvent(body);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                                ...
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            

                                                                                                                                                                                                                                            ManagementConsole使用委托来通知记录器和MonitorStatusHandler 。使用委托使我们能够轻松添加其他类,这些类也侦听传入的控制消息,而无需更改ProcessMessage 方法内的代码。

                                                                                                                                                                                                                                            The Man­age­ment­Con­sole class uses a del­eg­ate to notify the logger and the Mon­it­or­StatusH­and­ler. Using a del­eg­ate allows us to easily add other classes that also listen on in­com­ing con­trol mes­sages without having to change the code inside the Pro­cess­Mes­sage method.

                                                                                                                                                                                                                                            MonitorStatusHandler 类是分析传入控制消息数据的组件之一。 首先,此类检查传入消息正文中包含的 XML 文档是否具有根元素 < MonitorStatus>。如果是,它将消息正文加载到 XML 文档中,以提取 IDStatus元素中包含的相关字段。然后,它调用委托updateEvent,其类型为MonitorStatusUpdate 。管理控制台应用程序中任何感兴趣的类都可以向此委托添加回调方法,并在MonitorStatus发生时随时收到通知消息到达。该组件所要做的就是提供一个签名等于MonitorStatusUpdate 的方法的实现

                                                                                                                                                                                                                                            One of the com­pon­ents ana­lyz­ing in­com­ing con­trol mes­sage data is the Mon­it­or­StatusH­and­ler class. First, this class checks whether the XML doc­u­ment con­tained in the body of the in­com­ing mes­sage has the root ele­ment <Mon­it­or­Status>. If so, it loads the mes­sage body into an XML doc­u­ment to ex­tract the rel­ev­ant fields con­tained inside the ID and the Status ele­ments. It then in­vokes the del­eg­ate up­dateEvent, which is of type Mon­it­or­StatusUp­date. Any in­ter­ested class inside the man­age­ment con­sole ap­plic­a­tion can add a call­back method to this del­eg­ate and be no­ti­fied any time a Mon­it­or­Status mes­sage ar­rives. All the com­pon­ent has to do is provide an im­ple­ment­a­tion of a method with a sig­na­ture equal to Mon­it­or­StatusUp­date.

                                                                                                                                                                                                                                            监控状态处理程序
                                                                                                                                                                                                                                            公共委托 void MonitorStatusUpdate(String ID, int Status);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            公共类 MonitorStatusHandler
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                公共监视器状态更新更新事件;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                公共无效OnControlMessage(字符串体)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    XmlDocument doc = new XmlDocument();
                                                                                                                                                                                                                                                    doc.LoadXml(正文);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    XmlElement root = doc.DocumentElement;
                                                                                                                                                                                                                                                    if (root.Name == "MonitorStatus")
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        XmlNode statusNode = root.SelectSingleNode("状态");
                                                                                                                                                                                                                                                        XmlNode idNode = root.SelectSingleNode("ID");
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                        if (idNode!= null && statusNode != null)
                                                                                                                                                                                                                                                        {
                                                                                                                                                                                                                                                            String msgID = idNode.InnerText;
                                                                                                                                                                                                                                                            String msgStatus = statusNode.InnerText;
                                                                                                                                                                                                                                                            双倍resNum;
                                                                                                                                                                                                                                                            整数状态= 99;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                            if (Double.TryParse(msgStatus, NumberStyles.Integer,
                                                                                                                                                                                                                                                                                NumberFormatInfo.InvariantInfo,输出 resNum))
                                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                                状态 = (int)resNum;
                                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                                            updateEvent(msgID, 状态);
                                                                                                                                                                                                                                                        }
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            public del­eg­ate void Mon­it­or­StatusUp­date(String ID, int Status);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            public class Mon­it­or­StatusH­and­ler
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                public  Mon­it­or­StatusUp­date up­dateEvent;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                public void On­Con­trolMes­sage(String body)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    Xm­l­Doc­u­ment doc = new Xm­l­Doc­u­ment();
                                                                                                                                                                                                                                                    doc.LoadXml(body);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                    Xm­lEle­ment root = doc.Doc­u­men­tEle­ment;
                                                                                                                                                                                                                                                    if (root.Name == "Mon­it­or­Status")
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                                        Xm­l­Node status­Node = root.Se­lectSingleN­ode("Status");
                                                                                                                                                                                                                                                        Xm­l­Node idNode = root.Se­lectSingleN­ode("ID");
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                        if (idNode!= null && status­Node != null)
                                                                                                                                                                                                                                                        {
                                                                                                                                                                                                                                                            String msgID = idNode.In­ner­Text;
                                                                                                                                                                                                                                                            String msgStatus = status­Node.In­ner­Text;
                                                                                                                                                                                                                                                            Double resNum;
                                                                                                                                                                                                                                                            int status = 99;
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                            if (Double.Try­P­arse(msgStatus, Num­ber­Styles.In­teger,
                                                                                                                                                                                                                                                                                Num­ber­Form­at­Info.In­vari­an­tI­nfo, out resNum))
                                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                                status = (int)resNum;
                                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                                            up­dateEvent(msgID, status);
                                                                                                                                                                                                                                                        }
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            

                                                                                                                                                                                                                                            在我们的示例中,侦听由 MonitorStatusHandler触发的MonitorStatusUpdate 事件的前两个组件是两个用户界面控件,代表用户界面表单中的主要和辅助信用局服务。每个用户界面控件都会过滤事件以获取对于正在监视的相应组件唯一的标识符。当受监视组件的状态发生变化时,用户界面控件会更改组件的颜色。以下例程在显示窗体初始化期间执行,并将两个信用局显示控件绑定到管理控制台的 MonitorStatusHandler 。这段代码导致该方法每当控制台收到监视器状态更新消息时调用的控件的 OnMonitorStatusUpdate

                                                                                                                                                                                                                                            In our ex­ample, the first two com­pon­ents listen­ing to the Mon­it­or­StatusUp­date event triggered by the Mon­it­or­StatusH­and­ler are two user in­ter­face con­trols rep­res­ent­ing the primary and sec­ond­ary credit bureau ser­vice in the user in­ter­face form. Each user in­ter­face con­trol fil­ters the events for the iden­ti­fier that is unique to the re­spect­ive com­pon­ent that is being mon­itored. When the status of the mon­itored com­pon­ent changes, the user in­ter­face con­trol changes the color of the com­pon­ent. The fol­low­ing routine ex­ecutes during the ini­tial­iz­a­tion of the dis­play form and ties the two credit bureau dis­play con­trols to the mon­it­or­StatusH­and­ler of the man­age­ment con­sole. This code causes the method On­Mon­it­or­StatusUp­date of the con­trols to be in­voked whenever the con­sole re­ceives a mon­itor status update mes­sage

                                                                                                                                                                                                                                            控制台表单初始化
                                                                                                                                                                                                                                            console = new ManagementConsole(controlBusQueue, logFileName);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            主要信用局控制 =
                                                                                                                                                                                                                                                new ComponentStatusControl("Primary Credit Bureau", "PrimaryCreditService");
                                                                                                                                                                                                                                            PrimaryCreditBureauControl.Bounds =
                                                                                                                                                                                                                                                新矩形(300, 30, 组件宽度, 组件高度);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            二级信用局控制 =
                                                                                                                                                                                                                                                new ComponentStatusControl("二级征信局", "SecondaryCreditService");
                                                                                                                                                                                                                                            secondaryCreditBureauControl.Bounds =
                                                                                                                                                                                                                                                新矩形(300、130、分量宽度、分量高度);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            console.monitorStatusHandler.updateEvent += new
                                                                                                                                                                                                                                                 MonitorStatusUpdate(primaryCreditBureauControl.OnMonitorStatusUpdate);
                                                                                                                                                                                                                                            console.monitorStatusHandler.updateEvent += new
                                                                                                                                                                                                                                                 MonitorStatusUpdate(secondaryCreditBureauControl.OnMonitorStatusUpdate);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            con­sole = new Man­age­ment­Con­sole(con­trol­BusQueue, log­Fi­le­Name);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            primaryCred­it­Bur­eau­C­on­trol =
                                                                                                                                                                                                                                                new Com­pon­ent­StatusCon­trol("Primary Credit Bureau", "PrimaryCred­it­Ser­vice");
                                                                                                                                                                                                                                            primaryCred­it­Bur­eau­C­on­trol.Bounds =
                                                                                                                                                                                                                                                new Rect­angle(300, 30, COM­PON­EN­T_WIDTH, COM­PON­EN­T_HEIGHT);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            sec­ond­aryCred­it­Bur­eau­C­on­trol =
                                                                                                                                                                                                                                                new Com­pon­ent­StatusCon­trol("Sec­ond­ary Credit Bureau", "Sec­ond­aryCred­it­Ser­vice");
                                                                                                                                                                                                                                            sec­ond­aryCred­it­Bur­eau­C­on­trol.Bounds =
                                                                                                                                                                                                                                                new Rect­angle(300, 130, COM­PON­EN­T_WIDTH, COM­PON­EN­T_HEIGHT);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            con­sole.mon­it­or­StatusH­and­ler.up­dateEvent += new
                                                                                                                                                                                                                                                 Mon­it­or­StatusUp­date(primaryCred­it­Bur­eau­C­on­trol.On­Mon­it­or­StatusUp­date);
                                                                                                                                                                                                                                            con­sole.mon­it­or­StatusH­and­ler.up­dateEvent += new
                                                                                                                                                                                                                                                 Mon­it­or­StatusUp­date(sec­ond­aryCred­it­Bur­eau­C­on­trol.On­Mon­it­or­StatusUp­date);
                                                                                                                                                                                                                                            

                                                                                                                                                                                                                                            另一个监听MonitorStatusUpdate 事件的组件FailOverHandler 。该组件是一个非可视组件,它分析状态消息以确定是否应设置故障转移开关。如果监视器的状态发生了变化(我们使用由^运算符表示的逻辑 XOR),则FailOverHandler 会向指定的命令通道发送命令消息。在我们的例子中,此命令通道连接到前面描述的基于上下文的消息路由器,该路由器将开始将信用评分请求消息重新路由到不同的信用局提供商。

                                                                                                                                                                                                                                            An­other com­pon­ent listen­ing to the Mon­it­or­StatusUp­date events is the Fail­Over­Hand­ler. This com­pon­ent is a non­visual com­pon­ent that ana­lyzes status mes­sages to de­term­ine whether a fail­over switch should be set. If the status of the mon­itor has changed (we use a lo­gical XOR de­noted by the ^ op­er­ator), the Fail­Over­Hand­ler sends a com­mand mes­sage to the des­ig­nated com­mand chan­nel. In our case, this com­mand chan­nel is con­nec­ted to the con­text-based Mes­sage Router de­scribed earlier, which will start rerout­ing credit score re­quest mes­sages to a dif­fer­ent credit bureau pro­vider.

                                                                                                                                                                                                                                            故障转移处理程序类
                                                                                                                                                                                                                                            公共委托 void FailOverStatusUpdate(String ID, string Command);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            公共类故障转移处理程序
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                ...
                                                                                                                                                                                                                                                公共无效OnMonitorStatusUpdate(字符串ID,int状态)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    if (组件ID == ID)
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                        if (IsOK(状态) ^ IsOK(当前状态))
                                                                                                                                                                                                                                                        {
                                                                                                                                                                                                                                                            字符串命令 = IsOK(状态) ? “0”:“1”;
                                                                                                                                                                                                                                                            命令队列.Send(命令);
                                                                                                                                                                                                                                                            当前状态=状态;
                                                                                                                                                                                                                                                            updateEvent(ID, 命令);
                                                                                                                                                                                                                                                        }
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                protected bool IsOK(int 状态)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    返回(状态 == 0 || 状态 >= 99);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            public del­eg­ate void Fail­Over­StatusUp­date(String ID, string Com­mand);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            public class Fail­Over­Hand­ler
                                                                                                                                                                                                                                            {
                                                                                                                                                                                                                                                ...
                                                                                                                                                                                                                                                public void On­Mon­it­or­StatusUp­date(String ID, int status)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    if (com­pon­en­tID == ID)
                                                                                                                                                                                                                                                    {
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                        if (IsOK(status) ^ IsOK(cur­rent­Status))
                                                                                                                                                                                                                                                        {
                                                                                                                                                                                                                                                            String com­mand = IsOK(status) ? "0" : "1";
                                                                                                                                                                                                                                                            com­mandQueue.Send(com­mand);
                                                                                                                                                                                                                                                            cur­rent­Status = status;
                                                                                                                                                                                                                                                            up­dateEvent(ID, com­mand);
                                                                                                                                                                                                                                                        }
                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                pro­tec­ted bool IsOK(int status)
                                                                                                                                                                                                                                                {
                                                                                                                                                                                                                                                    return (status == 0 || status >= 99);
                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                            

                                                                                                                                                                                                                                            FailOverHandler调用updateEvent ,它FailOverStatusUpdate。与 MonitorStatusHandler 类似,我们可以注册任何实现此类型方法的组件,以便在FailOverHandler 更改状态时接收更新通知。在我们的示例中,我们注册视觉FailOverControl 来接收这些事件,以便在故障转移状态发生变化时它可以重新绘制。控制台用户界面初始化例程在这些组件之间建立连接:

                                                                                                                                                                                                                                            The Fail­Over­Hand­ler also in­vokes the up­dateEvent, which is a del­eg­ate of type Fail­Over­StatusUp­date. Sim­ilar to the Mon­it­or­StatusH­and­ler, we can re­gister any com­pon­ent that im­ple­ments a method of this type to re­ceive update no­ti­fic­a­tions whenever the Fail­Over­Hand­ler changes status. In our ex­ample, we re­gister the visual Fail­Over­Con­trol to re­ceive these events so that it can redraw whenever the fail­over status changes. The con­sole user in­ter­face ini­tial­iz­a­tion routine es­tab­lishes the con­nec­tion between these com­pon­ents:

                                                                                                                                                                                                                                            控制台表单初始化
                                                                                                                                                                                                                                            failOverControl = new FailOverControl("信用局故障转移", "PrimaryCreditService");
                                                                                                                                                                                                                                            failOverControl.Bounds = new 矩形(100, 80, ROUTER_WIDTH, COMPONENT_HEIGHT);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            故障处理程序 故障处理程序 =
                                                                                                                                                                                                                                                 新的 FailOverHandler(commandQueue, "PrimaryCreditService");
                                                                                                                                                                                                                                            console.monitorStatusHandler.updateEvent +=
                                                                                                                                                                                                                                                 新的 MonitorStatusUpdate(failOverHandler.OnMonitorStatusUpdate);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            failOverHandler.updateEvent += 新
                                                                                                                                                                                                                                                FailOverStatusUpdate(failOverControl.OnMonitorStatusUpdate);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            fail­Over­Con­trol = new Fail­Over­Con­trol("Credit Bureau Fail­over", "PrimaryCred­it­Ser­vice");
                                                                                                                                                                                                                                            fail­Over­Con­trol.Bounds = new Rect­angle(100, 80, ROUTER­_WIDTH, COM­PON­EN­T_HEIGHT);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            Fail­Over­Hand­ler fail­Over­Hand­ler =
                                                                                                                                                                                                                                                 new Fail­Over­Hand­ler(com­mandQueue, "PrimaryCred­it­Ser­vice");
                                                                                                                                                                                                                                            con­sole.mon­it­or­StatusH­and­ler.up­dateEvent +=
                                                                                                                                                                                                                                                 new Mon­it­or­StatusUp­date(fail­Over­Hand­ler.On­Mon­it­or­StatusUp­date);
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            fail­Over­Hand­ler.up­dateEvent += new
                                                                                                                                                                                                                                                Fail­Over­StatusUp­date(fail­Over­Con­trol.On­Mon­it­or­StatusUp­date);
                                                                                                                                                                                                                                            

                                                                                                                                                                                                                                            通过委托和事件连接管理控制台内的各个组件会形成松散耦合的体系结构。这种架构允许我们重用各个组件,并将它们重新组合成不同的星座,类似于本书开头介绍的管道和过滤器架构风格。本质上,使用委托传递到达控制总线的消息类似于创建应用程序内部的发布-订阅通道。由于控制总线事件到达点对点通道,因此我们必须使用单个使用者,然后该使用者将事件发布到应用程序内任何感兴趣的订阅者。

                                                                                                                                                                                                                                            Con­nect­ing the in­di­vidual com­pon­ents inside the man­age­ment con­sole through del­eg­ates and events res­ults in a loosely coupled ar­chi­tec­ture. This ar­chi­tec­ture allows us to reuse the in­di­vidual com­pon­ents and re­com­pose them into dif­fer­ent con­stel­la­tions sim­ilar to the Pipes and Fil­ters ar­chi­tec­tural style in­tro­duced at the be­gin­ning of the book. Es­sen­tially, passing mes­sages ar­riv­ing on the Con­trol Bus by using del­eg­ates re­sembles cre­at­ing an ap­plic­a­tion-in­ternal Pub­lish-Sub­scribe Chan­nel. Be­cause the con­trol bus events arrive on a Point-to-Point Chan­nel, we have to use a single con­sumer, which then pub­lishes the event to any in­ter­ested sub­scriber inside the ap­plic­a­tion.

                                                                                                                                                                                                                                            以下协作图说明了管理控制台内各个组件之间的事件传播。

                                                                                                                                                                                                                                            The fol­low­ing col­lab­or­a­tion dia­gram il­lus­trates the pro­paga­tion of events between the in­di­vidual com­pon­ents inside the man­age­ment con­sole.

                                                                                                                                                                                                                                            管理控制台内事件的传播

                                                                                                                                                                                                                                            Pro­paga­tion of Events Inside the Man­age­ment Con­sole

                                                                                                                                                                                                                                            图形/12inf10.gif

                                                                                                                                                                                                                                            使用用户界面控制台可视化各个组件之间的消息流是一种强大的系统管理工具。一些供应商提供的开发套件允许设计人员直观地排列组件并连接其输入和输出端口以创建分布式消息流应用程序。例如,福州共赢靓号网络科技有限公司的Tifosi产品(http://www.fiorano.com)包括分布式应用程序编辑器,即使每个组件可能在不同的机器或平台上执行,它也允许从单个GUI设计分布式解决方案。该工具使用控制总线将所有分布式组件连接到中央管理和监控控制台。

                                                                                                                                                                                                                                            Using a user in­ter­face con­sole to visu­al­ize the mes­sage flow between in­di­vidual com­pon­ents is a power­ful sys­tems man­age­ment tool. Some vendors in­clude de­vel­op­ment suites that allow de­sign­ers to visu­ally ar­range com­pon­ents and con­nect their input and out­puts ports to create dis­trib­uted mes­sage-flow ap­plic­a­tion. For ex­ample, Fior­ano's Tifosi product (http://www.fior­ano.com) in­cludes the Dis­trib­uted Ap­plic­a­tions Com­poser that allows the design of a dis­trib­uted solu­tion from a single GUI even though each com­pon­ent may ex­ecute on a dif­fer­ent ma­chine or plat­form. This tool uses a Con­trol Bus to con­nect all dis­trib­uted com­pon­ents to a cent­ral man­age­ment and mon­it­or­ing con­sole.

                                                                                                                                                                                                                                            我们的简单示例要求管理控制台对各个组件之间的可视连接进行硬编码,例如,在故障转移路由器和代表信用局服务的图标之间画一条线。许多集成工具允许用户使用 GUI 从一开始就设计解决方案。这种方法可以轻松地使用相同的图形设计来显示解决方案的状态。

                                                                                                                                                                                                                                            Our simple ex­ample re­quires the man­age­ment con­sole to hard-code the visual con­nec­tion between the in­di­vidual com­pon­ents, for ex­ample, to draw a line between the fail­over router and the icons rep­res­ent­ing the credit bureau ser­vices. Many in­teg­ra­tion tools allow the user to design the solu­tion from the be­gin­ning using a GUI. This ap­proach makes it easy to use the same graph­ical design to dis­play the status of the solu­tion.

                                                                                                                                                                                                                                            或者,我们可以分析现有消息传递解决方案中的消息流以创建系统的图形表示。有两种基本方法可以执行此类分析:静态和动态。静态分析检查每个组件发布和订阅的通道。如果一个组件发布到另一组件订阅的同一通道,则该工具可以在两个组件之间绘制一条连接线。许多 EAI 工具套件(例如 TIBCO ActiveEnterprise)将此类信息存储在中央存储库中,从而使此类分析变得更加容易。第二种方法使用动态分析通过检查流经系统的各个消息,并根据到达特定组件的消息来源对组件之间的连接进行逆向工程。如果系统中的消息包含消息历史记录,则此任务将大大简化。如果没有消息历史记录的帮助如果每条消息都包含指定消息发送者的字段(许多系统包含这样的字段用于身份验证目的),我们仍然可以重建消息流。

                                                                                                                                                                                                                                            Al­tern­at­ively, we can ana­lyze the mes­sage flow in an ex­ist­ing mes­saging solu­tion to create a graph­ical rep­res­ent­a­tion of the system. There are two fun­da­mental ap­proaches to per­form this type of ana­lysis: static and dy­namic. A static ana­lysis ex­am­ines the chan­nels that each com­pon­ent pub­lishes and sub­scribes to. If one com­pon­ent pub­lishes to the same chan­nel an­other com­pon­ent sub­scribes to, the tool can draw a con­nect­ing line between the two com­pon­ents. Many EAI tool suites, for ex­ample, TIBCO Act­iveEn­ter­prise, store this type of in­form­a­tion in a cent­ral re­pos­it­ory, making this type of ana­lysis much easier. The second ap­proach uses dy­namic ana­lysis by in­spect­ing in­di­vidual mes­sages flow­ing through the system and re­verse-en­gin­eer­ing con­nec­tions between com­pon­ents based on the origin of mes­sages ar­riv­ing at a par­tic­u­lar com­pon­ent. This task is greatly sim­pli­fied if the mes­sages in the system con­tain a Mes­sage His­tory. Without the help of a Mes­sage His­tory, we can still re­con­struct the flow of mes­sages if each mes­sage con­tains a field spe­cify­ing the sender of the mes­sage (many sys­tems in­clude such a field for au­then­tic­a­tion pur­poses).

                                                                                                                                                                                                                                            本例的局限性

                                                                                                                                                                                                                                            Lim­it­a­tions of This Ex­ample

                                                                                                                                                                                                                                            为了使这个例子适合一章的范围,我们必须做出一些简化的假设。例如,当主信用局服务出现故障时,我们的故障转移机制不会处理已经排队的消息,这些消息将保持排队状态,直到服务恢复为止。贷款经纪人能够继续运行,因为它将传入的响应消息与回复消息相关联,但与“卡住”消息关联的贷款报价请求将不会得到处理,直到主信用局服务重新上线。为了提高故障转移场景中的响应时间,我们应该实现一个重新发送功能,允许贷款经纪人为那些在失败的服务之前无限期排队的消息重新发出请求消息。或者,故障转移路由器可以存储自上次确认服务的正确功能以来到达的所有请求消息。如果检测到服务故障,路由器可能会重新发送所有这些消息,因为其中一些消息可能未得到正确处理。这种方法可能会导致重复的请求消息(以及相关的回复消息),但由于信用局服务和贷款经纪人都是 如果检测到服务故障,路由器可能会重新发送所有这些消息,因为其中一些消息可能未得到正确处理。这种方法可能会导致重复的请求消息(以及相关的回复消息),但由于信用局服务和贷款经纪人都是 如果检测到服务故障,路由器可能会重新发送所有这些消息,因为其中一些消息可能未得到正确处理。这种方法可能会导致重复的请求消息(以及相关的回复消息),但由于信用局服务和贷款经纪人都是幂等接收器这不会导致任何问题,重复的回复消息将被简单地忽略。

                                                                                                                                                                                                                                            In order to fit this ex­ample into the scope of a single chapter, we had to make some sim­pli­fy­ing as­sump­tions. For ex­ample, our fail­over mech­an­ism does not deal with the mes­sages that are already queued up when the primary credit bureau ser­vice fail­sthese mes­sages remain queued up until the ser­vice is re­in­stated. The loan broker is able to con­tinue func­tion­ing be­cause it cor­rel­ates in­com­ing re­sponse mes­sages to reply mes­sages, but the loan quote re­quests as­so­ci­ated with the "stuck" mes­sages will not be pro­cessed until the primary credit bureau ser­vice comes back online. In order to im­prove re­sponse times in a fail­over scen­ario, we should im­ple­ment a resend func­tion that allows the loan broker to re­is­sue re­quest mes­sages for those mes­sages that are queued up in­def­in­itely in front of a failed ser­vice. Al­tern­at­ively, the fail­over router could store all re­quest mes­sages that have ar­rived since the cor­rect func­tion of the ser­vice was last con­firmed. If a ser­vice fail­ure is de­tec­ted, the router could resend all these mes­sages be­cause some of them might not have been pro­cessed cor­rectly. This ap­proach can lead to du­plic­ate re­quest mes­sages (and as­so­ci­ated reply mes­sages), but since both the credit bureau ser­vice and the loan broker are Idem­potent Re­ceiv­ers this does not cause any prob­lems­du­plic­ate reply mes­sages are simply ig­nored.

                                                                                                                                                                                                                                            此示例仅演示了可以使用前一章中的模式实现的系统管理功能的一小部分。例如,我们可以监控所有组件的消息流量、设置性能阈值、让每个组件发送心跳消息等等。事实上,向分布式消息传递解决方案添加强大的系统管理可能需要与原始解决方案一样多(或更多)的设计和实现工作。

                                                                                                                                                                                                                                            This ex­ample demon­strated only a small subset of the system man­age­ment func­tions that can be im­ple­men­ted with the pat­terns in the pre­vi­ous chapter. For ex­ample, we could mon­itor mes­sage traffic across all com­pon­ents, set per­form­ance thresholds, have each com­pon­ent send heart­beat mes­sages, and more. In fact, adding robust sys­tems man­age­ment to a dis­trib­uted mes­saging solu­tion can re­quire as much (or more) design and im­ple­ment­a­tion effort as the ori­ginal solu­tion.

                                                                                                                                                                                                                                              第 13 章实践中的集成模式

                                                                                                                                                                                                                                              Chapter 13. Integration Patterns in Practice

                                                                                                                                                                                                                                              案例研究:债券定价系统

                                                                                                                                                                                                                                              Case Study: Bond Pricing System

                                                                                                                                                                                                                                              作者:乔纳森·西蒙

                                                                                                                                                                                                                                              by Jonathan Simon

                                                                                                                                                                                                                                              您很容易远离大量模式或模式语言。模式是可重用形式的想法的抽象。通常,模式的通用性使其非常有用,但也使其难以掌握。有时,帮助理解模式的最好方法是一个现实世界的例子,而不是可能发生的人为场景,而是实际发生的情况和将要发生的情况。

                                                                                                                                                                                                                                              It is easy to dis­tance your­self from a large col­lec­tion of pat­terns or a pat­tern lan­guage. Pat­terns are the ab­strac­tion of an idea in a re­usable form. Often, the very gen­eric nature of pat­terns that makes them so useful also makes them hard to grasp. Some­times, the best thing to help un­der­stand pat­terns is a real-world ex­am­plenot a con­trived scen­ario of what could happen, but what ac­tu­ally hap­pens and what will happen.

                                                                                                                                                                                                                                              本章通过发现过程应用模式来解决问题。我们讨论的系统是一个债券交易系统,我从最初的设计到生产使用了两年。我们探索遇到的场景和问题以及如何用模式解决它们。这涉及选择模式的决策过程以及如何组合和调整模式以满足系统的需求。这一切都是在考虑到实际系统中遇到的压力的情况下完成的,包括业务需求、客户决策、架构和技术要求以及遗留系统集成。这种方法的目的是通过实际应用提供对模式本身的更清晰的理解。

                                                                                                                                                                                                                                              This chapter ap­plies pat­terns to solve prob­lems using a dis­cov­ery pro­cess. The system we dis­cuss is a bond trad­ing system that I worked with for two years from ini­tial design through pro­duc­tion. We ex­plore scen­arios and prob­lems that were en­countered and how to solve them with pat­terns. This in­volves the de­cision pro­cess of choos­ing a pat­tern as well as how to com­bine and adjust pat­terns to suit the needs of the system. This is all done taking into ac­count the forces en­countered in real sys­tems, in­clud­ing busi­ness re­quire­ments, client de­cisions, ar­chi­tec­tural and tech­nical re­quire­ments, and legacy system in­teg­ra­tion. The intent of this ap­proach is to provide a clearer un­der­stand­ing of the pat­terns them­selves through prac­tical ap­plic­a­tion.

                                                                                                                                                                                                                                              建立一个系统

                                                                                                                                                                                                                                              Build­ing a System

                                                                                                                                                                                                                                              华尔街一家大型投资银行着手建立债券定价系统,以简化其债券交易柜台的工作流程。目前,债券交易商必须将大量债券的价格发送到几个不同的交易场所,每个交易场所都有自己的用户界面。该系统的目标是最大限度地减少所有债券定价的细节,并在单个封装的用户界面中结合特定于债券市场的高级分析功能。这意味着通过各种通信协议与多个组件进行集成和通信。系统的高级流程如下所示:

                                                                                                                                                                                                                                              A major Wall Street in­vest­ment bank sets out to build a bond pri­cing system in an effort to stream­line the work­flow of its bond trad­ing desk. Cur­rently, bond traders have to send prices for a large number of bonds to sev­eral dif­fer­ent trad­ing venues, each with its own user in­ter­face. The goal for the system is to min­im­ize the minu­tiae of pri­cing all of the bonds com­bined with ad­vanced ana­lytic func­tion­al­ity spe­cific to the bond market in a single en­cap­su­lated user in­ter­face. This means in­teg­ra­tion and com­mu­nic­a­tion with sev­eral com­pon­ents over vari­ous com­mu­nic­a­tions pro­to­cols. The high-level flow of the system looks like this:

                                                                                                                                                                                                                                              高层次流程

                                                                                                                                                                                                                                              High-Level Flow

                                                                                                                                                                                                                                              图形/13inf01.gif

                                                                                                                                                                                                                                              首先,市场数据进入系统。市场数据是有关债券价格和其他属性的数据,代表人们愿意在自由市场上买卖债券的价格。市场数据立即发送到更改数据的分析引擎。分析是指用于改变债券价格和其他属性的金融应用的数学函数。这些是通用函数,它们使用输入变量来根据特定键定制函数的结果。将在每个交易者桌面上运行的客户端应用程序将根据每个交易者配置分析引擎,控制交易者定价的每种债券的分析细节。一旦将分析应用于市场数据,

                                                                                                                                                                                                                                              First, market data comes into the system. Market data is data re­gard­ing the price and other prop­er­ties of the bond, rep­res­ent­ing what people are will­ing to buy and sell the bond for on the free market. The market data is im­me­di­ately sent to the ana­lyt­ics engine that alters the data. Ana­lyt­ics refers to math­em­at­ical func­tions for fin­an­cial ap­plic­a­tions that alter the prices and other at­trib­utes of bonds. These are gen­eric func­tions that use input vari­ables to tailor the res­ults of the func­tion to a par­tic­u­lar bond. The client ap­plic­a­tion that will run on each trader desktop will con­fig­ure the ana­lyt­ics engine on a per-trader basis, con­trolling the spe­cif­ics of the ana­lyt­ics for each bond the trader is pri­cing. Once the ana­lyt­ics are ap­plied to the market data, the mod­i­fied data is sent out to vari­ous trad­ing venues where traders from other firms can buy or sell the bonds.

                                                                                                                                                                                                                                              具有模式的架构

                                                                                                                                                                                                                                              Ar­chi­tec­ture with Pat­terns

                                                                                                                                                                                                                                              通过对系统中数据流的概述,我们可以解决在设计过程中遇到的一些架构问题。让我们看看迄今为止我们所知道的情况。交易者需要在 Windows NT 和 Solaris 工作站上具有非常灵敏的应用程序。因此,我们决定将客户端应用程序实现为 Java 胖客户端,因为它具有平台无关性并且能够快速响应用户输入和市场数据。在服务器端,我们继承了我们的系统将使用的遗留 C++ 组件。市场数据组件与 TIBCO 信息总线 (TIB) 消息传递基础设施进行通信。

                                                                                                                                                                                                                                              With this over­view of the data flow in the system, we can ap­proach some of the ar­chi­tec­tural prob­lems we en­counter during the design pro­cess. Let's take a look at what we know to date. Traders need a very re­spons­ive ap­plic­a­tion on both Win­dows NT and Sol­aris work­sta­tions. There­fore, we de­cided to im­ple­ment the client ap­plic­a­tion as a Java thick client be­cause of its plat­form-in­de­pend­ence and its abil­ity to quickly re­spond to user input and market data. On the server side, we are in­her­it­ing legacy C++ com­pon­ents that our system will util­ize. The market data com­pon­ents com­mu­nic­ate with the TIBCO In­form­a­tion Bus (TIB) mes­saging in­fra­struc­ture.

                                                                                                                                                                                                                                              我们继承以下组件:

                                                                                                                                                                                                                                              We are in­her­it­ing the fol­low­ing com­pon­ents:

                                                                                                                                                                                                                                              • 市场数据喂价服务器: 将传入的市场数据发布到 TIB。

                                                                                                                                                                                                                                              • Market Data Price Feed Server: Pub­lishes in­com­ing market data to the TIB.

                                                                                                                                                                                                                                              • 分析引擎: 对传入的市场数据进行分析并将修改后的市场数据广播到 TIB。

                                                                                                                                                                                                                                              • Ana­lyt­ics Engine: Per­forms ana­lyt­ics on in­com­ing market data and broad­casts the mod­i­fied market data to the TIB.

                                                                                                                                                                                                                                              • 贡献服务器: 执行与交易场所的所有通信。交易场所是不受银行控制的第三方组件。

                                                                                                                                                                                                                                              • Con­tri­bu­tion Server: Per­forms all com­mu­nic­a­tion with trad­ing venues. The trad­ing venues are third-party com­pon­ents not con­trolled by the bank.

                                                                                                                                                                                                                                              遗留市场数据子系统

                                                                                                                                                                                                                                              Legacy Market Data Sub­sys­tem

                                                                                                                                                                                                                                              图形/13inf02.gif

                                                                                                                                                                                                                                              遗留贡献子系统

                                                                                                                                                                                                                                              Legacy Con­tri­bu­tion Sub­sys­tem

                                                                                                                                                                                                                                              图形/13inf03.gif

                                                                                                                                                                                                                                              我们需要决定各个子系统(Java 胖客户端、市场数据和贡献)如何进行通信。我们可以让胖客户端直接与遗留服务器通信,但这需要客户端上有太多的业务逻辑。相反,我们将构建一对 Java 网关来与旧服务器进行通信:市场数据的定价网关和用于将价格发送到交易场所的贡献网关。这将实现与这些领域相关的业务逻辑的良好封装。系统中当前的组件如下所示。连接标记为“???” 表明我们仍然不确定某些组件将如何通信。

                                                                                                                                                                                                                                              We need to decide how the sep­ar­ate sub­sys­tems (Java thick client, market data, and con­tri­bu­tion) are going to com­mu­nic­ate. We could have the thick client com­mu­nic­ate dir­ectly with the legacy serv­ers, but that would re­quire too much busi­ness logic on the client. In­stead, we'll build a pair of Java gate­ways to com­mu­nic­ate with the legacy server­sthe pri­cing gate­way for market data and the con­tri­bu­tion gate­way for send­ing prices to trad­ing venues. This will achieve nice en­cap­su­la­tion of the busi­ness logic re­lated to these areas. The cur­rent com­pon­ents in the system are shown below. The con­nec­tions marked as "???" in­dic­ate that we are still unsure how some of the com­pon­ents will com­mu­nic­ate.

                                                                                                                                                                                                                                              系统及其组件

                                                                                                                                                                                                                                              The System and Its Com­pon­ents

                                                                                                                                                                                                                                              图形/13inf04.gif

                                                                                                                                                                                                                                              第一个通信问题是如何集成Java胖客户端和两个Java网关组件以交换数据。让我们看看本书中建议的四种集成样式:文件传输、共享数据库、远程过程调用和消息传递。 我们可以立即排除共享数据库,因为我们想在客户端和数据库之间创建一个抽象层,并且我们不希望客户端中有数据库访问代码。文件传输同样可以被排除,因为需要最小的延迟来确保将当前价格发送到交易场所。这让我们有一个选择远程过程调用和消息传递

                                                                                                                                                                                                                                              The first com­mu­nic­a­tion ques­tion is how to in­teg­rate the Java thick client and the two Java gate­way com­pon­ents in order to ex­change data. Let's look at the four in­teg­ra­tion styles sug­ges­ted in this book: File Trans­fer, Shared Data­base, Remote Pro­ced­ure In­voc­a­tion, and Mes­saging. We can rule out Shared Data­base im­me­di­ately be­cause we want to create a layer of ab­strac­tion between the client and the data­base, and we don't want to have data­base access code in the client. File Trans­fer, can sim­il­arly be ruled out, since min­imal latency is re­quired to ensure cur­rent prices are sent out to the trad­ing venues. This leaves us with a choice between Remote Pro­ced­ure In­voc­a­tion and Mes­saging.

                                                                                                                                                                                                                                              Java 平台为远程过程调用和消息传递提供内置支持。RPC 风格的集成可以使用远程方法调用 (RMI)、CORBA 或 Enterprise JavaBeans (EJB) 来实现。Java 消息传递服务 (JMS) 是用于消息传递样式集成的通用 API。这两种集成风格都很容易用 Java 实现。

                                                                                                                                                                                                                                              The Java plat­form provides built-in sup­port for both Remote Pro­ced­ure In­voc­a­tion and Mes­saging. RPC-style in­teg­ra­tion can be achieved using Remote Method In­voc­a­tion (RMI), CORBA, or En­ter­prise Java­Beans (EJB). The Java Mes­saging Ser­vice (JMS) is the common API for mes­saging-style in­teg­ra­tion. Both in­teg­ra­tion styles are easy to im­ple­ment in Java.

                                                                                                                                                                                                                                              那么,远程过程调用和消息传递哪个更适合这个项目呢?系统中只有一个定价网关实例和一个贡献网关实例,但通常有许多胖客户端同时连接到这些服务(每个恰好在特定时间登录的债券交易者对应一个)。此外,银行希望这是一个可以在其他应用程序中使用的通用定价系统。因此,除了未知数量的胖客户端之外,可能还有未知数量的其他应用程序使用来自网关的定价数据。

                                                                                                                                                                                                                                              So, which will work better for this pro­ject, Remote Pro­ced­ure In­voc­a­tion or Mes­saging ? There's only one in­stance of the pri­cing gate­way and one in­stance of the con­tri­bu­tion gate­way in the system, but usu­ally many thick cli­ents sim­ul­tan­eously con­nect to these ser­vices (one for each bond trader that hap­pens to be logged in at a par­tic­u­lar time). Fur­ther­more, the bank would like this to be a gen­eric pri­cing system that can be util­ized in other ap­plic­a­tions. So, be­sides an un­known number of thick cli­ents, there may be an un­known number of other ap­plic­a­tions using the pri­cing data coming out of the gate­ways.

                                                                                                                                                                                                                                              胖客户端(或使用定价数据的其他应用程序)可以相当轻松地使用 RPC 调用网关来获取定价数据并调用处理。然而,定价数据会不断发布,而某些客户只对某些数据感兴趣,因此及时将相关数据提供给适当的客户可能会很困难。客户端可以轮询网关,但这会产生大量开销。网关最好在数据可用时立即向客户端提供数据。然而,这将要求每个网关跟踪哪些客户端当前处于活动状态以及哪些客户端需要哪些特定数据;然后,当有新的数据可用时(每秒会发生多次),网关必须向每个感兴趣的客户端发起 RPC,以将数据传递给客户端。理想情况下,所有客户端都应该同时收到通知,因此每个 RPC 都需要在自己的并发线程中进行。这可以工作,但是很快就会变得非常复杂。

                                                                                                                                                                                                                                              A thick client (or other ap­plic­a­tion using the pri­cing data) can fairly easily use RPC to make calls to the gate­ways to get pri­cing data and invoke pro­cess­ing. How­ever, pri­cing data will con­stantly be pub­lished, and cer­tain cli­ents are only in­ter­ested in cer­tain data, so get­ting the rel­ev­ant data to the proper cli­ents in a timely manner could be dif­fi­cult. The cli­ents could poll the gate­ways, but that will create a lot of over­head. It would be better for the gate­ways to make the data avail­able to the cli­ents as soon as it is avail­able. This, how­ever, will re­quire each gate­way to keep track of which cli­ents are cur­rently active and which want what par­tic­u­lar data; then, when a new piece of data be­comes avail­able (which will happen nu­mer­ous times per second), the gate­way will have to make an RPC to each in­ter­ested client to pass the data to the client. Ideally, all cli­ents should be no­ti­fied sim­ul­tan­eously, so each RPC needs to be made in its own con­cur­rent thread. This can work, but is get­ting very com­plic­ated very fast.

                                                                                                                                                                                                                                              消息传递极大地通过消息传递,我们可以为不同类型的定价数据定义单独的渠道。然后,当网关获取新数据时,它将包含该数据的消息添加到的发布-订阅通道。同时,所有对某种类型的数据感兴趣的客户端都会在该类型的通道上监听。通过这种方式,网关可以轻松地将新数据发送给感兴趣的任何人,而无需知道有多少侦听器应用程序或它们是什么。

                                                                                                                                                                                                                                              Mes­saging greatly sim­pli­fies this prob­lem. With Mes­saging, we can define sep­ar­ate chan­nels for the dif­fer­ent types of pri­cing data. Then, when a gate­way gets a new piece of data, it will add a mes­sage con­tain­ing that data to the Pub­lish-Sub­scribe Chan­nel for that data­type. Mean­while, all cli­ents in­ter­ested in a cer­tain type of data will listen on the chan­nel for that type. In this way, the gate­ways can easily send out new data to whomever is in­ter­ested without need­ing to know how many listener ap­plic­a­tions there are or what they are.

                                                                                                                                                                                                                                              客户端仍然需要能够调用网关中的行为。由于只有两个网关,并且在同步调用方法时客户端可能会阻塞,因此可以使用 RPC 相当轻松地实现这些客户端到网关的调用。然而,由于我们已经使用消息传递进行网关到客户端的通信,因此消息可能也是实现客户端到网关通信的好方法。

                                                                                                                                                                                                                                              The cli­ents still need to be able to invoke be­ha­vior in the gate­ways as well. Since there are ever only two gate­ways, and the client can prob­ably block while the method is in­voked syn­chron­ously, these client-to-gate­way in­voc­a­tions can fairly easily be im­ple­men­ted using RPC. How­ever, since we are already using mes­saging for gate­way-to-client com­mu­nic­a­tion, mes­sages are prob­ably just as good a way to im­ple­ment client-to-gate­way com­mu­nic­a­tion as well.

                                                                                                                                                                                                                                              因此,网关和客户端之间的所有通信都将通过消息传递来完成。由于所有组件都是用 Java 编写的,因此 JMS 为消息传递系统提供了一个简单的选择。这有效地创建了一个消息总线或一个体系结构,使未来的系统可以与当前系统集成,而无需对消息传递基础设施进行很少的更改或无需更改。这样,银行开发的其他应用程序就可以轻松使用该应用程序的业务功能。

                                                                                                                                                                                                                                              There­fore, all com­mu­nic­a­tion between the gate­ways and the cli­ents will be ac­com­plished through mes­saging. Be­cause all of the com­pon­ents are writ­ten in Java, JMS presents an easy choice for the mes­saging system. This is ef­fect­ively cre­at­ing a Mes­sage Bus or an ar­chi­tec­ture that will make it pos­sible for future sys­tems to in­teg­rate with the cur­rent system with little or no changes to the mes­saging in­fra­struc­ture. This way, the busi­ness func­tion­al­ity of the ap­plic­a­tion can be easily used by other ap­plic­a­tions the bank de­vel­ops.

                                                                                                                                                                                                                                              与JMS通信的 Java 组件

                                                                                                                                                                                                                                              Java Com­pon­ents Com­mu­nic­at­ing with JMS

                                                                                                                                                                                                                                              图形/13inf05.gif

                                                                                                                                                                                                                                              JMS 只是一个规范,我们需要决定一个符合 JMS 的消息传递系统。我们决定使用 IBM MQSeries JMS,因为该银行是一家“IBM 商店”,使用 WebSphere 应用程序服务器和许多其他 IBM 产品。因此,我们将使用 MQSeries,因为我们已经拥有适当的支持基础设施和该产品的站点许可证。

                                                                                                                                                                                                                                              JMS is simply a spe­cific­a­tion, and we need to decide on a JMS-com­pli­ant mes­saging system. We de­cided to use IBM MQSer­ies JMS be­cause the bank is an "IBM shop," using Web­Sphere ap­plic­a­tion serv­ers and many other IBM products. As a result, we will use MQSer­ies, since we already have a sup­port in­fra­struc­ture in place and a site li­cense of the product.

                                                                                                                                                                                                                                              下一个问题是如何将 MQSeries 消息传递系统与独立的 C++ 贡献服务器以及基于 TIBCO 的市场数据和分析引擎服务器连接起来。我们需要一种方法让 MQSeries 使用者能够访问 TIB 消息。但如何呢?也许我们可以使用消息转换器模式将 TIB 消息转换为 MQSeries 消息。尽管 MQSeries 的 C++ 客户端充当消息转换器,但使用它会牺牲 JMS 服务器的独立性。尽管 TIBCO 确实有 Java API,但客户架构师和经理拒绝了它。因此,消息翻译器方法必须被放弃。

                                                                                                                                                                                                                                              The next ques­tion is how to con­nect the MQSer­ies mes­saging system with the stan­dalone C++ con­tri­bu­tion server and the TIBCO-based market data and ana­lyt­ics engine serv­ers. We need a way for the MQSer­ies con­sumers to have access to the TIB mes­sages. But how? Per­haps we could use the Mes­sage Trans­lator pat­tern to trans­late TIB mes­sages into MQSer­ies mes­sages. Al­though the C++ client for MQSer­ies serves as a Mes­sage Trans­lator, using it would sac­ri­fice JMS server in­de­pend­ence. And al­though TIBCO does have a Java API, the cus­tomer ar­chi­tect and man­ager have re­jec­ted it. As a result, the Mes­sage Trans­lator ap­proach has to be aban­doned.

                                                                                                                                                                                                                                              从 TIB 服务器到 MQSeries 服务器的桥梁需要 C++ 和 Java 之间的通信。我们可以使用 CORBA,但是消息传递又如何呢?仔细观察消息转换器模式会发现它与通信协议的使用中的通道适配器相关。通道适配器的核心将非消息系统连接到消息系统。连接两个消息传递系统的一对通道适配器是消息传递桥

                                                                                                                                                                                                                                              The bridge from the TIB server to the MQSer­ies server re­quires com­mu­nic­a­tion between C++ and Java. We could use CORBA, but then what about the mes­saging? A closer look at the Mes­sage Trans­lator pat­tern shows it is re­lated to the Chan­nel Ad­apter in its use of com­mu­nic­a­tion pro­to­cols. The heart of a Chan­nel Ad­apter is to con­nect non-mes­saging sys­tems to mes­saging sys­tems. A pair of chan­nel ad­apters that con­nects two mes­saging sys­tems is a Mes­saging Bridge.

                                                                                                                                                                                                                                              消息传递桥的目的是将消息从一个消息传递系统传输到另一个消息传递系统。这正是我们正在做的事情,增加了语言内 Java 到 C++ 通信的复杂性。我们可以使用通道适配器和CORBA的组合来实现跨语言消息传递桥。我们将构建两个轻量级通道适配器服务器,一台使用 C++ 管理与 TIB 的通信,另一台使用 Java 管理与 JMS 的通信。这两个通道适配器,它们是消息端点他们自己将通过 CORBA 相互通信。就像我们选择 MQSeries 一样,我们将使用 CORBA 而不是 JNI,因为它是公司标准。消息桥实现了看似不兼容的消息系统和不同语言之间的有效模拟消息翻译。

                                                                                                                                                                                                                                              The pur­pose of a Mes­saging Bridge is to trans­fer mes­sages from one mes­saging system to an­other. This is ex­actly what we are doing with the added com­plex­ity of the in­t­ralan­guage Java to C++ com­mu­nic­a­tion. We can im­ple­ment the cross-lan­guage Mes­saging Bridge using a com­bin­a­tion of Chan­nel Ad­apters and CORBA. We will build two light­weight Chan­nel Ad­apter serv­ers, one in C++ man­aging com­mu­nic­a­tion with the TIB and one in Java man­aging com­mu­nic­a­tion with JMS. These two Chan­nel Ad­apters, which are Mes­sage En­d­points them­selves, will com­mu­nic­ate with each other via CORBA. Like our choice for MQSer­ies, we will use CORBA rather than JNI, since it is a com­pany stand­ard. The mes­saging bridge im­ple­ments the ef­fect­ively sim­u­lated mes­sage trans­la­tion between seem­ingly in­com­pat­ible mes­saging sys­tems and dif­fer­ent lan­guages.

                                                                                                                                                                                                                                              使用通道适配器的消息传递桥

                                                                                                                                                                                                                                              Mes­saging Bridge Using Chan­nel Ad­apters

                                                                                                                                                                                                                                              图形/13inf06.gif

                                                                                                                                                                                                                                              下页的下图显示了当前的系统设计,包括网关和其他组件。这是模式应用的一个很好的例子。我们将两个通道适配器与非消息传递协议相结合来实现消息传递桥模式,有效地使用一种模式来实现另一种模式。此外,我们更改了通道适配器的上下文,以使用非消息跨语言翻译协议链接两个消息系统,而不是将消息系统连接到非消息系统。

                                                                                                                                                                                                                                              The next figure on the fol­low­ing page shows the cur­rent system design, in­clud­ing the gate­ways and other com­pon­ents. This is a good ex­ample of pat­tern ap­plic­a­tion. We com­bined two Chan­nel Ad­apters with a non-mes­saging pro­tocol to im­ple­ment the Mes­saging Bridge pat­tern, ef­fect­ively using one pat­tern to im­ple­ment an­other pat­tern. Ad­di­tion­ally, we changed the Chan­nel Ad­apters' con­text to link two mes­saging sys­tems with a non-mes­saging cross-lan­guage trans­la­tion pro­tocol rather than con­nect­ing a mes­saging system to a non-mes­saging system.

                                                                                                                                                                                                                                              带有通道适配器的当前系统

                                                                                                                                                                                                                                              The Cur­rent System with the Chan­nel Ad­apters

                                                                                                                                                                                                                                              图形/13inf07.gif

                                                                                                                                                                                                                                              构建渠道

                                                                                                                                                                                                                                              Struc­tur­ing Chan­nels

                                                                                                                                                                                                                                              使用模式的关键不仅是知道何时使用哪种模式,而且还知道如何最有效地使用它。每个模式的实现都必须考虑技术平台的细节以及其他设计标准。本节应用相同的发现过程来找到在市场数据服务器与分析引擎通信的上下文中发布-订阅通道的最有效使用。

                                                                                                                                                                                                                                              A key to work­ing with pat­terns is know­ing not only when to use which pat­tern, but also know­ing how to most ef­fect­ively use it. Each pat­tern im­ple­ment­a­tion has to take into ac­count spe­cif­ics of the tech­no­logy plat­form as well as other design cri­teria. This sec­tion ap­plies the same dis­cov­ery pro­cess to find the most ef­fi­cient use of the Pub­lish-Sub­scribe Chan­nel in the con­text of the market data server com­mu­nic­at­ing with the ana­lyt­ics engine.

                                                                                                                                                                                                                                              实时市场数据源自市场数据源,这是一个在 TIB 上广播市场数据的 C++ 服务器。市场数据源为其发布价格的每种债券使用单独的发布-订阅通道。这可能看起来有点极端,因为每个新债券都需要自己的新渠道。但这并没有那么严重,因为您实际上不需要在 TIBCO 中创建通道。相反,频道由一组称为主题的分层主题名称引用。 然后,TIBCO 服务器按主题过滤单个消息流,将每个唯一的主题发送到单个虚拟通道。这导致了一个非常轻量级的消息通道。

                                                                                                                                                                                                                                              Real-time market data ori­gin­ates with market data feed, a C++ server that broad­casts market data on the TIB. The market data feed uses a sep­ar­ate Pub­lish-Sub­scribe Chan­nel for each bond it is pub­lish­ing prices for. This may seem a little ex­treme, since each new bond needs its own new chan­nel. But this is not so severe, since you do not ac­tu­ally need to create chan­nels in TIBCO. Rather, chan­nels are ref­er­enced by a hier­arch­ical set of topic names called sub­jects. The TIBCO server then fil­ters a single mes­sage flow by sub­ject, send­ing each unique sub­ject to a single vir­tual chan­nel. This res­ults in a very light­weight mes­sage chan­nel.

                                                                                                                                                                                                                                              我们可以创建一个在几个频道上发布的系统,订阅者只能收听他们感兴趣的价格。这将要求订阅者使用消息过滤器选择性消费者过滤整个数据流以获取感兴趣的债券价格,决定是否应在收到每条消息时对其进行处理。鉴于市场数据是在债券专用渠道上发布的,订阅者可以注册以获取一系列债券的最新信息。这有效地允许订阅者通过有选择地订阅频道并仅接收感兴趣的更新而不是在接收到消息后做出决定来“过滤”。值得注意的是,使用多个渠道来避免过滤是消息传递渠道的非标准使用。然而,在 TIBCO 技术的背景下,我们真正决定是实施我们自己的过滤器还是利用 TIBCO 内置的通道过滤,而不是是否使用这么多通道。

                                                                                                                                                                                                                                              We could create a system that pub­lishes on a few chan­nels, and sub­scribers could listen only for prices they are in­ter­ested in. This would re­quire sub­scribers to use a Mes­sage Filter or Se­lect­ive Con­sumer to filter the entire data flow for in­ter­est­ing bond prices, de­cid­ing whether each mes­sage should be pro­cessed as it is re­ceived. Given that the market data is pub­lished on bond-ded­ic­ated chan­nels, sub­scribers can re­gister for up­dates on a series of bonds. This ef­fect­ively allows sub­scribers to "filter" by se­lect­ively sub­scrib­ing to chan­nels and only re­ceiv­ing up­dates of in­terest rather than de­cid­ing after the mes­sage is re­ceived. It is im­port­ant to note that using mul­tiple chan­nels to avoid fil­ter­ing is a non­stand­ard use of mes­saging chan­nels. In con­text of the TIBCO tech­no­logy, how­ever, we are really de­cid­ing whether to im­ple­ment our own fil­ters or util­ize the chan­nel fil­ter­ing built into TIBCOrather than whether to use so many chan­nels.

                                                                                                                                                                                                                                              我们需要设计的下一个组件是分析引擎,这是另一个 C++/TIB 服务器,它将修改市场数据并将其重新广播到 TIB。尽管它超出了我们的 Java/JMS 开发范围,但我们正在与 C++ 团队密切合作来设计它,因为我们是分析引擎的主要客户。当前的问题是找到最有效地转播新修改的市场数据的渠道结构。

                                                                                                                                                                                                                                              The next com­pon­ent we need to design is the ana­lyt­ics engine, an­other C++/TIB server that will modify the market data and rebroad­cast it to the TIB. Al­though it is out of the scope of our Java/JMS de­vel­op­ment, we are work­ing closely with the C++ team to design it, since we are the ana­lyt­ics engine's primary cus­tomer. The prob­lem at hand is to find the chan­nel struc­ture that most ef­fi­ciently rebroad­casts the newly mod­i­fied market data.

                                                                                                                                                                                                                                              由于我们已经为每只债券拥有一个从市场数据价格馈送继承的专用消息通道,因此修改市场数据并在债券专用消息通道上重新广播修改后的市场数据是合乎逻辑的。但这是行不通的,因为修改债券价格的分析是针对特定交易者的。如果我们在 bond消息通道上重新广播修改后的数据,我们将通过用交易者特定的数据替换通用市场数据来破坏数据完整性。但是,我们可以为在同一渠道上发布的特定于交易者的市场数据提供不同的消息类型,从而允许订阅者决定他们感兴趣的消息,以避免破坏数据完整性。但随后客户将必须实施自己的过滤器来为其他交易者分离消息。此外,订阅者收到的消息也会大幅增加,给他们带来不必要的负担。

                                                                                                                                                                                                                                              Since we already have one ded­ic­ated Mes­sage Chan­nel per bond in­her­ited from the market data price feed, it would be lo­gical to modify the market data and rebroad­cast the mod­i­fied market data on the bond ded­ic­ated Mes­sage Chan­nel. But this will not work since the ana­lyt­ics modi­fy­ing the bonds prices are trader-spe­cific. If we rebroad­cast the mod­i­fied data on the bond Mes­sage Chan­nel, we will des­troy the data in­teg­rity by re­pla­cing gen­eric market data with trader-spe­cific data. How­ever, we could have a dif­fer­ent mes­sage type for trader-spe­cific market data that we pub­lish on the same chan­nel, al­low­ing sub­scribers to decide which mes­sage they are in­ter­ested in to avoid des­troy­ing the data in­teg­rity. But then cli­ents will have to im­ple­ment their own fil­ters to sep­ar­ate out mes­sages for other traders. Ad­di­tion­ally, there will a sub­stan­tial in­crease in mes­sages re­ceived by sub­scribers, pla­cing an un­ne­ces­sary burden on them.

                                                                                                                                                                                                                                              有两种选择:

                                                                                                                                                                                                                                              There are two op­tions:

                                                                                                                                                                                                                                              1. 每个交易者一个通道: 每个交易者都有一个指定的通道来修改市场数据。这样,原始市场数据保持不变,每个交易者应用程序都可以监听其特定交易者的消息通道以获取修改后的价格更新。

                                                                                                                                                                                                                                              2. One chan­nel per trader: Each trader has a des­ig­nated chan­nel for the mod­i­fied market data. This way, the ori­ginal market data re­mains intact, and each trader ap­plic­a­tion can listen to its spe­cific trader's Mes­sage Chan­nel for the mod­i­fied price up­dates.

                                                                                                                                                                                                                                              3. 每个交易者每个债券一个通道:为每个交易者每个债券 创建一个消息通道,仅用于该债券的修改后的市场数据。例如,债券 ABC 的市场数据将发布在频道“Bond ABC”上,而交易者 A 的修改后的市场数据将发布在频道“Trader A, Bond ABC”上,交易者 B 的修改后的市场数据将发布在频道“Trader”上。 B、邦德 ABC”,等等。

                                                                                                                                                                                                                                              4. One chan­nel per trader per bond: Create one Mes­sage Chan­nel per trader, per bond, solely for the mod­i­fied market data of that bond. For ex­ample, the market data for bond ABC would be pub­lished on chan­nel "Bond ABC," while the mod­i­fied market data for trader A would be pub­lished on chan­nel "Trader A, Bond ABC," mod­i­fied market data for trader B on chan­nel "Trader B, Bond ABC," and so on.

                                                                                                                                                                                                                                              每个交易者一个通道

                                                                                                                                                                                                                                              One Chan­nel per Trader

                                                                                                                                                                                                                                              图形/13inf08.gif

                                                                                                                                                                                                                                              每个交易者每个债券一个通道

                                                                                                                                                                                                                                              One Chan­nel per Bond per Trader

                                                                                                                                                                                                                                              图形/13inf09.gif

                                                                                                                                                                                                                                              每种方法都有优点和缺点。例如,每个键的方法使用更多的消息通道。在最坏的情况下,消息通道的数量将是债券总数乘以交易者数量。我们可以对将要创建的通道数量设定上限,因为我们知道只有大约 20 个交易者,而且他们对债券的定价绝不会超过几百种。这使得上限低于 10,000 个范围,与市场数据价格馈送使用的近 100,000 个消息通道相比,这并不算奇怪。此外,由于我们使用的是 TIB,并且消息通道非常便宜,因此消息通道不是一个严重的问题。然而,从管理角度来看,消息通道的绝对数量每次添加债券时,必须为每个交易者维护一个通道。这在一个非常动态的系统中可能会很严重。然而,我们的系统本质上是静态的。它还具有用于自动管理消息通道的基础结构。这与使用类似方法的遗留组件的继承架构相结合,最大限度地减少了缺点。这并不是说我们应该创建不必要的过多消息通道。相反,我们可以实现一种使用大量消息通道的架构方法当有理由的时候。

                                                                                                                                                                                                                                              There are ad­vant­ages and dis­ad­vant­ages to each ap­proach. The per-bond ap­proach, for ex­ample, uses a lot more Mes­sage Chan­nels. In the worst-case scen­ario, the number of Mes­sage Chan­nels will be the total number of bonds mul­ti­plied by the number of traders. We can put upper bounds on the number of chan­nels that will be cre­ated, since we know that there are only around 20 traders and they never price more than a couple hun­dred bonds. This puts the upper limit below the 10,000 range, which is not so out­land­ish com­pared to the nearly 100,000 Mes­sage Chan­nels the market data price feed is using. Also, since we are using the TIB, and Mes­sage Chan­nels are quite in­ex­pens­ive, the number of Mes­sage Chan­nels is not a severe issue. How­ever, the sheer number of Mes­sage Chan­nels could be a prob­lem from a man­age­ment per­spect­ive. Every time a bond is added, a chan­nel for each trader must be main­tained. This could be severe in a very dy­namic system. Our system, how­ever, is es­sen­tially static. It also has an in­fra­struc­ture for auto­mat­ic­ally man­aging Mes­sage Chan­nels. This com­bined with the in­her­ited ar­chi­tec­ture of a legacy com­pon­ent using a sim­ilar ap­proach min­im­izes the down­side. This is not to say we should make an un­ne­ces­sar­ily ex­cess­ive number of Mes­sage Chan­nels. Rather, we can im­ple­ment an ar­chi­tec­tural ap­proach that uses a large number of Mes­sage Chan­nels when there is a reason.

                                                                                                                                                                                                                                              这种情况是有原因,归结为逻辑位置。如果我们实施每个交易者的方法,分析引擎需要逻辑来对输入和输出通道进行分组。这是因为分析引擎的输入通道是针对每个债券的,而输出消息通道是针对每个交易者的,要求分析引擎将来自特定交易者的多个债券的所有分析输入路由到交易者特定的输出消息频道。这有效地将分析引擎转变为基于内容的路由器,为我们的应用程序实现自定义路由逻辑。

                                                                                                                                                                                                                                              And there is a reason in this case, which comes down to the loc­a­tion of logic. If we im­ple­ment the per-trader ap­proach, the Ana­lyt­ics Engine needs logic to group input and output chan­nels. This is be­cause the input chan­nels from the Ana­lyt­ics Engine are per-bond, and the output Mes­sage Chan­nels would be per-trader, re­quir­ing the Ana­lyt­ics Engine to route all ana­lyt­ics input from mul­tiple bonds for a par­tic­u­lar trader to a trader-spe­cific output Mes­sage Chan­nel. This ef­fect­ively turns the ana­lyt­ics engine into a Con­tent-Based Router to im­ple­ment custom rout­ing logic for our ap­plic­a­tion.

                                                                                                                                                                                                                                              遵循消息总线结构,分析引擎是一个通用服务器,可以由系统中的其他几个系统使用,因此我们不希望将系统特定的功能置于云中。另一方面,每只债券的方法是有效的,因为交易者拥有债券价格的分析输出的想法是公司接受的做法。每个债券的方法保持了市场数据源的消息通道分离完整,同时添加了更多消息通道。在到达客户端之前,我们需要一个基于内容的路由器将这几个通道组合成可管理数量的通道。我们不希望交易者桌面上运行的客户端应用程序监听数千或数万个消息通道。现在的问题是把基于内容的路由器放在哪里。我们可以简单地让 C++/TIB通道适配器将所有消息转发到单个消息通道上的定价网关。这很糟糕,原因有两个:我们将在 C++ 和 Java 之间拆分业务逻辑,并且我们将失去单独消息通道的好处在 TIB 端,这允许我们避免稍后在数据流中进行过滤。查看我们的 Java 组件,我们可以将其放置在定价网关中,或者在定价网关和客户端之间创建一个中间组件。

                                                                                                                                                                                                                                              Fol­low­ing the Mes­sage Bus struc­ture, the Ana­lyt­ics Engine is a gen­eric server that could be used by sev­eral other sys­tems in the system, so we don't want to cloud it with system-spe­cific func­tion­al­ity. On the other hand, the per-bond ap­proach works, since the idea of a trader owning the ana­lyt­ics output of bond prices is a com­pany ac­cep­ted prac­tice. The per-bond ap­proach keeps the Mes­sage Chan­nel sep­ar­a­tion of the market data feed intact while adding sev­eral more Mes­sage Chan­nels. Before we reach the client, we want a Con­tent-Based Router to com­bine these sev­eral chan­nels into a man­age­able number of chan­nels. We don't want the client ap­plic­a­tion run­ning on the trader's desktop to be listen­ing to thou­sands or tens of thou­sands of Mes­sage Chan­nels. Now the ques­tion be­comes where to put the Con­tent-Based Router. We could simply have the C++/TIB Chan­nel Ad­apter for­ward all of the mes­sages to the pri­cing gate­way on a single Mes­sage Chan­nel. This is bad for two reas­ons: We would be split­ting up the busi­ness logic between C++ and Java, and we would lose the be­ne­fit of the sep­ar­ate Mes­sage Chan­nels on the TIB side that allows us to avoid fil­ter­ing later in the data flow. Look­ing at our Java com­pon­ents, we could either place it in the pri­cing gate­way or create an in­ter­me­di­ary com­pon­ent between the pri­cing gate­way and the client.

                                                                                                                                                                                                                                              理论上,如果我们坚持基于债券的消息通道分离一直到客户端,定价网关将使用与定价网关和分析引擎相同的通道结构重新广播定价信息。这意味着 JMS 中所有绑定专用 TIB 通道的复制。即使我们在定价网关和客户端之间创建一个中间组件,定价网关仍然必须复制 JMS 中的所有通道。然而,直接在定价网关中实现逻辑使我们能够避免在 JMS 中复制大量通道,从而允许我们以每个交易者一个的顺序创建数量少得多的通道。定价网关通过 C++/TIB 自行注册通道适配器作为系统中每个交易者的每个债券的消费者。然后,定价网关仅向每个特定客户端转发与该特定交易者相关的消息。这样,我们在JMS 端仅使用少量的,同时最大化 TIB 端分离的好处。

                                                                                                                                                                                                                                              In theory, if we per­sisted the bond-based sep­ar­a­tion of Mes­sage Chan­nels all the way to the client, the pri­cing gate­way would rebroad­cast pri­cing in­form­a­tion with the same chan­nel struc­ture as the pri­cing gate­way and Ana­lyt­ics Engine. This means a du­plic­a­tion of all of the bond-ded­ic­ated TIB chan­nels in JMS. Even if we create an in­ter­me­di­ary com­pon­ent between the pri­cing gate­way and the client, the pri­cing gate­way will still have to du­plic­ate all of the chan­nels in JMS. How­ever, im­ple­ment­ing logic dir­ectly in the pri­cing gate­way allows us to avoid du­plic­at­ing the large number of chan­nels in JM­Sal­low­ing us to create a much smal­ler number of chan­nels in the order of one per trader. The pri­cing gate­way re­gisters itself through the C++/TIB Chan­nel Ad­apter as a con­sumer for each bond of every trader in the system. Then, the pri­cing gate­way for­wards each spe­cific client only the mes­sages re­lated to that par­tic­u­lar trader. This way, we use only a small number of Mes­sage Chan­nels on the JMS end while max­im­iz­ing the be­ne­fit of the sep­ar­a­tion on the TIB end.

                                                                                                                                                                                                                                              完整的市场数据流向客户

                                                                                                                                                                                                                                              The Com­plete Market Data Flow to the Client

                                                                                                                                                                                                                                              图形/13inf10.gif

                                                                                                                                                                                                                                              消息通道布局讨论是一个很好的例子,说明了集成模式的重要性。目标是弄清楚如何有效地使用消息通道。仅仅说使用模式是不够的。您需要弄清楚如何最好地实施它并将其合并到您的系统中以解决手头的问题。此外,这个例子还展示了商业力量的实际行动。如果我们可以在任何组件中实现业务逻辑,我们就可以采用每个交易者的方法,并通过更少的渠道实现总体上更简单的方法。

                                                                                                                                                                                                                                              The Mes­sage Chan­nel layout dis­cus­sion is a good ex­ample of how in­teg­rat­ing pat­terns is im­port­ant. The goal was to figure out how to ef­fect­ively use the Mes­sage Chan­nels. Saying you use a pat­tern isn't enough. You need to figure out how to best im­ple­ment it and in­cor­por­ate it into your system to solve the prob­lems at hand. Ad­di­tion­ally, this ex­ample shows busi­ness forces in action. If we could im­ple­ment busi­ness logic in any of our com­pon­ents, we could have gone with the per-trader ap­proach and im­ple­men­ted an over­all more simple ap­proach with many fewer chan­nels.

                                                                                                                                                                                                                                              选择消息通道

                                                                                                                                                                                                                                              Se­lect­ing a Mes­sage Chan­nel

                                                                                                                                                                                                                                              现在我们已经了解了 Java/JMS 组件和 C++/TIBCO 组件之间的通信机制,并且了解了一些消息通道结构,接下来我们需要决定 Java 组件应该使用哪种类型的 JMS消息通道来进行通信。在我们可以在不同的消息渠道之间进行选择之前JMS 中可用,让我们看一下系统的高层消息流。我们有两个与客户沟通的网关(定价和贡献)。市场数据从定价网关流向客户端,定价网关将其发送到贡献网关。客户端应用程序向定价网关发送消息以更改应用于每个债券的分析。贡献网关还向客户端应用程序发送消息,将价格更新的状态转发到不同的交易场所。

                                                                                                                                                                                                                                              Now that we know the mech­an­ics of the com­mu­nic­a­tion between the Java/JMS com­pon­ents and the C++/ TIBCO com­pon­ents, and we have seen some Mes­sage Chan­nel struc­tur­ing, we need to decide which type of JMS Mes­sage Chan­nels the Java com­pon­ents should use to com­mu­nic­ate. Before we can choose between the dif­fer­ent Mes­sage Chan­nels avail­able in JMS, let's look at the high-level mes­sage flow of the system. We have two gate­ways (pri­cing and con­tri­bu­tion) com­mu­nic­at­ing with the client. Market data flows to the client from the pri­cing gate­way, which sends it out to the con­tri­bu­tion gate­way. The client ap­plic­a­tion sends mes­sages to the pri­cing gate­way to alter the ana­lyt­ics being ap­plied to each bond. The con­tri­bu­tion gate­way also sends mes­sages to the client ap­plic­a­tion re­lay­ing the status of the price up­dates to the dif­fer­ent trad­ing venues.

                                                                                                                                                                                                                                              系统消息流

                                                                                                                                                                                                                                              The System Mes­sage Flow

                                                                                                                                                                                                                                              图形/13inf11.gif

                                                                                                                                                                                                                                              JMS 规范描述了两种消息通道类型:队列(点对点通道)主题(发布-订阅通道) 。回想一下,使用发布-订阅的情况是让所有感兴趣的消费者都能接收到一条消息,而使用点对点的情况是确保只有一个合格的消费者接收到特定的消息。

                                                                                                                                                                                                                                              The JMS spe­cific­a­tion de­scribes two Mes­sage Chan­nel types: Queue (a Point-to-Point Chan­nel) and Topic (a Pub­lish-Sub­scribe Chan­nel). Recall that the case for using pub­lish-sub­scribe is to enable all in­ter­ested con­sumers to re­ceive a mes­sage, while the case for using point-to-point is to ensure that only one eli­gible con­sumer re­ceives a par­tic­u­lar mes­sage.

                                                                                                                                                                                                                                              许多系统只是将消息广播到所有客户端应用程序,让每个单独的客户端应用程序自行决定是否处理特定消息。这对我们的应用程序不起作用,因为大量的市场数据消息被发送到每个客户端应用程序。如果我们向不感兴趣的交易者广播市场数据更新,我们将不必要地浪费客户端处理器周期来决定是否处理市场数据更新。

                                                                                                                                                                                                                                              Many sys­tems would simply broad­cast mes­sages to all client ap­plic­a­tions, leav­ing each in­di­vidual client ap­plic­a­tion to decide for itself whether or not to pro­cess a par­tic­u­lar mes­sage. This will not work for our ap­plic­a­tion, since a large number of market data mes­sages are being sent to each client ap­plic­a­tion. If we broad­cast market data up­dates to un­in­ter­ested traders, we will be un­ne­ces­sar­ily wast­ing client pro­cessor cycles de­cid­ing whether or not to pro­cess a market data update.

                                                                                                                                                                                                                                              点对点通道最初但业务要求是交易者可以同时登录多台机器。如果交易者同时在两个工作站登录并发送点对点价格更新,则只有两个客户端应用程序之一会收到该消息。这是因为点对点通道上只有一个消费者请注意,只有交易者客户端应用程序每组中的第一个会收到该消息。

                                                                                                                                                                                                                                              Point-to-Point Chan­nels ini­tially sounds like a good choice, since the cli­ents are send­ing mes­sages to unique serv­ers, and vice versa. But it was a busi­ness re­quire­ment that traders may be logged in to mul­tiple ma­chines at the same time. If we have a trader logged in at two work­sta­tions sim­ul­tan­eously and a point-to-point price update is sent, only one of the two client ap­plic­a­tions will get the mes­sage. This is be­cause only one con­sumer on a Point-to-Point Chan­nel can re­ceive a par­tic­u­lar mes­sage (see figure on the next page). Notice that only the first of each group of a trader's client ap­plic­a­tions re­ceives the mes­sage.

                                                                                                                                                                                                                                              价格更新的点对点消息传送

                                                                                                                                                                                                                                              Point-to-Point Mes­saging for Price Up­dates

                                                                                                                                                                                                                                              图形/13inf12.gif

                                                                                                                                                                                                                                              我们可以使用收件人列表来解决这个问题,它将消息发布到预期收件人列表,保证只有收件人列表中的客户端才会收到消息。使用这种模式,系统可以创建收件人列表,其中包含与每个交易者相关的所有客户端应用程序实例。发送与特定交易者相关的消息将依次将该消息发送至收件人列表中的每个应用程序。这保证了与特定交易者相关的所有客户端应用程序实例都会收到该消息。这种方法的缺点是需要大量的实现逻辑来管理接收者和发送消息。

                                                                                                                                                                                                                                              We could solve this using Re­cip­i­ent List, which pub­lishes mes­sages to a list of in­ten­ded re­cip­i­ents, guar­an­tee­ing that only cli­ents in the re­cip­i­ent list will re­ceive mes­sages. Using this pat­tern, the system could create re­cip­i­ent lists with all client ap­plic­a­tion in­stances re­lated to each trader. Send­ing a mes­sage re­lated to a par­tic­u­lar trader would in turn send the mes­sage to each ap­plic­a­tion in the re­cip­i­ent list. This guar­an­tees all client ap­plic­a­tion in­stances re­lated to a par­tic­u­lar trader would re­ceive the mes­sage. The down­side of this ap­proach is that it re­quires quite a bit of im­ple­ment­a­tion logic to manage the re­cip­i­ents and dis­patch mes­sages.

                                                                                                                                                                                                                                              价格更新的收件人列表

                                                                                                                                                                                                                                              Re­cip­i­ent List for Price Up­dates

                                                                                                                                                                                                                                              图形/13inf13.gif

                                                                                                                                                                                                                                              尽管点对点可以工作,但让我们看看是否有更好的方法。使用发布-订阅通道,系统可以在特定于交易者的通道而不是特定于客户端应用程序的通道上广播消息。这样,处理单个交易者消息的所有客户端应用程序都将接收并处理该消息(参见下图)。

                                                                                                                                                                                                                                              Even though point-to-point could be made to work, let's see if there is a better way. Using Pub­lish-Sub­scribe Chan­nels, the system could broad­cast mes­sages on trader-spe­cific chan­nels rather than client ap­plic­a­tion­spe­cific chan­nels. This way, all client ap­plic­a­tions pro­cess­ing mes­sages for a single trader would re­ceive and pro­cess the mes­sage (see the fol­low­ing figure).

                                                                                                                                                                                                                                              发布-订阅价格更新消息

                                                                                                                                                                                                                                              Pub­lish-Sub­scribe Mes­saging for Price Up­dates

                                                                                                                                                                                                                                              图形/13inf14.gif

                                                                                                                                                                                                                                              使用发布-订阅通道的缺点是服务器组件不能保证唯一的消息处理。一个服务器组件的多个实例可能会被实例化,并且每个实例都会处理相同的消息,这可能会发送出无效的价格。

                                                                                                                                                                                                                                              The down­side of using Pub­lish-Sub­scribe Chan­nels is that unique mes­sage pro­cess­ing is not guar­an­teed with the server com­pon­ents. It would be pos­sible for mul­tiple in­stances of a server com­pon­ent to be in­stan­ti­ated and for each in­stance to pro­cess the same mes­sage, pos­sibly send­ing out in­valid prices.

                                                                                                                                                                                                                                              回想一下系统消息流,每个消息通道只有一个通信方向是令人满意的。 采用发布-订阅的服务器到客户端通信令人满意,而客户端到服务器通信则不然;采用点对点的客户端到服务器通信令人满意,而服务器到客户端则不令人满意。由于不需要在两个方向上使用相同的消息通道,因此我们可以仅在一个方向上使用每个消息通道。客户端到服务器的通信将采用点对点的方式实现,而服务器到客户端的通信将采用发布-订阅的方式实现。使用消息通道的这种组合,该系统受益于使用点对点消息传递和发布-订阅的多播性质与服务器组件的直接通信,而没有任何缺点。

                                                                                                                                                                                                                                              Re­call­ing the system mes­sage flow, only a single com­mu­nic­a­tion dir­ec­tion is sat­is­fact­ory with each Mes­sage Chan­nel. Server-to-client com­mu­nic­a­tion with pub­lish-sub­scribe is sat­is­fact­ory, while client-to-server com­mu­nic­a­tion is not, and client-to-server com­mu­nic­a­tion with point-to-point is sat­is­fact­ory, while server-to-client is not. Since there is no need to use the same Mes­sage Chan­nel in both dir­ec­tions, we can use each Mes­sage Chan­nel in only one dir­ec­tion. Client-to-server com­mu­nic­a­tion will be im­ple­men­ted with point-to-point, while server-to-client com­mu­nic­a­tion will be im­ple­men­ted with pub­lish-sub­scribe. Using this com­bin­a­tion of Mes­sage Chan­nels, the system be­ne­fits from direct com­mu­nic­a­tion with the server com­pon­ents using point-to-point mes­saging and the mul­tic­ast nature of pub­lish-sub­scribe without either of the draw­backs.

                                                                                                                                                                                                                                              具有通道类型的消息流

                                                                                                                                                                                                                                              Mes­sage Flow with Chan­nel Types

                                                                                                                                                                                                                                              图形/13inf15.gif

                                                                                                                                                                                                                                              用模式解决问题

                                                                                                                                                                                                                                              Prob­lem Solv­ing with Pat­terns

                                                                                                                                                                                                                                              模式是工具,模式的集合是工具箱。他们帮助解决问题。有些人认为模式只在设计过程中才有用。按照工具箱的比喻,这就像说工具只在你建造房子时才有用,而在你修理它时则没有用。事实上,如果应用得当,模式在整个项目中都是一个有用的工具。在以下部分中,我们使用上一节中使用的相同模式探索过程来解决我们当前工作系统中的问题。

                                                                                                                                                                                                                                              Pat­terns are tools, and col­lec­tions of pat­terns are tool­boxes. They help solve prob­lems. Some think that pat­terns are only useful during design. Fol­low­ing the tool­box ana­logy, this is like saying that tools are only useful when you build a house, not when you fix it. The fact is that pat­terns are a useful tool through­out a pro­ject when ap­plied well. In the fol­low­ing sec­tions we use the same pat­tern ex­plor­a­tion pro­cess we used in the pre­vi­ous sec­tion to solve prob­lems in our now work­ing system.

                                                                                                                                                                                                                                              闪烁的市场数据更新

                                                                                                                                                                                                                                              Flash­ing Market Data Up­dates

                                                                                                                                                                                                                                              交易者希望在收到债券的新市场数据时表格单元格会闪烁,清楚地表明变化。Java客户端收到带有新数据的消息,这会触发客户端数据缓存更新并最终在表中闪烁。问题是更新非常频繁。GUI 线程堆栈变得过载并最终冻结客户端,因为它无法响应用户交互。我们将假设刷新已被优化,并通过更新过程专注于消息的数据流。对性能数据的检查显示客户端应用程序每秒接收多个更新;两次更新的间隔时间可以不到一毫秒。聚合器似乎可以帮助减慢消息流的两种模式消息过滤器

                                                                                                                                                                                                                                              Traders want table cells to flash when new market data is re­ceived for a bond, clearly in­dic­at­ing changes. The Java client re­ceives mes­sages with new data, which trig­gers a client data cache update and even­tu­ally flash­ing in the table. The prob­lem is that up­dates come quite fre­quently. The GUI thread stack is be­com­ing over­loaded and even­tu­ally freez­ing the client, since it can't re­spond to user in­ter­ac­tion. We will assume that the flash­ing is op­tim­ized and con­cen­trate on the data flow of mes­sages through the up­dat­ing pro­cess. An ex­am­in­a­tion of per­form­ance data shows the client ap­plic­a­tion is re­ceiv­ing sev­eral up­dates a second; two up­dates can occur less than a mil­li­second apart. Two pat­terns that seem like they could help slow down the mes­sage flow are Ag­greg­ator and Mes­sage Filter.

                                                                                                                                                                                                                                              第一个想法是实现消息过滤器,通过丢弃参考消息后短时间内收到的更新来控制消息流的速度。举个例子,假设我们要忽略 5 毫秒内的消息。消息过滤器可以缓存最后可接受的消息的时间,并丢弃在接下来的 5 毫秒内收到的任何消息。虽然其他应用程序可能无法承受这种程度的数据丢失,但由于价格更新的频率,这在我们的系统中是完全可以接受的。

                                                                                                                                                                                                                                              A first thought is to im­ple­ment a Mes­sage Filter to con­trol the speed of the mes­sage flow by throw­ing out up­dates re­ceived a short time after the ref­er­ence mes­sage. As an ex­ample, let's say that we are going to ignore mes­sages within 5 mil­li­seconds of each other. The Mes­sage Filter could cache the time of the last ac­cept­able mes­sage and throw out any­thing re­ceived within the next 5 mil­li­seconds. While other ap­plic­a­tions may not be able to with­stand data loss to such an extent, this is per­fectly ac­cept­able in our system due to the fre­quency of price up­dates.

                                                                                                                                                                                                                                              基于时间的消息过滤器

                                                                                                                                                                                                                                              Time-Based Mes­sage Filter

                                                                                                                                                                                                                                              图形/13inf16.gif

                                                                                                                                                                                                                                              这种方法的问题在于并非所有数据字段都会同时更新。每个债券都有大约 50 个向用户显示的数据字段,包括价格。我们意识到并非每条消息中的每个字段都会更新。如果系统忽略连续的消息,它很可能会丢弃重要的数据。

                                                                                                                                                                                                                                              The prob­lem with this ap­proach is that not all data fields are up­dated at the same time. Each bond has ap­prox­im­ately 50 data fields dis­played to the user, in­clud­ing price. We real­ize that not every field is up­dated in every mes­sage. If the system ig­nores con­sec­ut­ive mes­sages, it may very well be throw­ing out im­port­ant data.

                                                                                                                                                                                                                                              另一个令人感兴趣的模式是聚合器。聚合用于管理将多个相关消息协调为单个消息,从而可能减少消息流。聚合可以保留第一个聚合消息中的绑定数据的副本,然后仅更新连续消息中的新字段或更改字段。最终,聚合的债券数据将通过消息传递给客户端。现在,我们假设聚合器将消息。稍后,我们将探索另一种选择。

                                                                                                                                                                                                                                              The other pat­tern of in­terest is the Ag­greg­ator. The Ag­greg­ator is used to manage the re­con­cili­ation of mul­tiple, re­lated mes­sages into a single mes­sage, po­ten­tially re­du­cing the mes­sage flow. The Ag­greg­ator could keep a copy of the bond data from the first ag­greg­ated mes­sage, then update only new or changed fields from suc­cess­ive mes­sages. Even­tu­ally, the ag­greg­ated bond data will be passed in a mes­sage to the client. For now, let's assume that the Ag­greg­ator will send a mes­sage every 5 mil­li­seconds like the Mes­sage Filter. Later, we'll ex­plore an­other al­tern­at­ive.

                                                                                                                                                                                                                                              部分连续更新的聚合器

                                                                                                                                                                                                                                              Ag­greg­ator with Par­tial Suc­cess­ive Up­dates

                                                                                                                                                                                                                                              图形/13inf17.gif

                                                                                                                                                                                                                                              与任何其他模式一样并不是灵丹妙药。它有其优点和缺点,需要探索。一个潜在的缺点是实施聚合器在我们的例子中,只有当许多消息在相对较短的时间内传入同一键时,才会大大减少消息流量。然而,如果 Java 客户端仅收到所有交易者债券的一个字段的更新,我们将一事无成。例如,如果我们在指定时间范围内收到 1,000 条具有 4 个利息债券的消息,我们将在该时间范围内将消息流从 1,000 条减少到 4 条。或者,如果我们在同一时间范围内收到 1,000 条消息和 750 个利息债券,我们会将消息流从 1,000 条减少到 750 条:与付出的努力相比,收获相对较小。对消息更新的快速分析证明,Java 客户端接收到许多消息更新同一键的字段,因此也是相关消息。所以,聚合器实际上

                                                                                                                                                                                                                                              The Ag­greg­ator, like any other pat­tern, is not a silver bullet; it has its pluses and minuses that need to be ex­plored. One po­ten­tial minus is that im­ple­ment­ing an Ag­greg­ator would reduce the mes­sage traffic by a great amount in our case only if many mes­sages are coming in within a re­l­at­ively short time re­gard­ing the same bond. How­ever, we would ac­com­plish noth­ing if the Java client re­ceives up­dates for only one field across all of the traders' bonds. For ex­ample, if we re­ceive 1,000 mes­sages in a spe­cified time­frame with four bonds of in­terest, we would reduce the mes­sage flow from 1,000 to four mes­sages over that time­frame. Al­tern­at­ively, if we re­ceive 1,000 mes­sages in the same time­frame with 750 bonds of in­terest, we will have re­duced the mes­sage flow from 1,000 to 750 mes­sages: re­l­at­ively little gain for the amount of effort. A quick ana­lysis of the mes­sage up­dates proves that the Java client re­ceives many mes­sages up­dat­ing fields of the same bondand there­fore re­lated mes­sages. So, Ag­greg­ator is in fact a good de­cision.

                                                                                                                                                                                                                                              剩下的就是确定聚合器如何知道何时发送它聚合的消息。该模式描述了聚合器了解何时发送消息的一些算法。其中包括使聚合器在经过一定时间后、在数据集中的所有必填字段完成后发送其内容的算法等。所有这些方法的问题在于,控制消息流的是聚合器(而不是客户端),并且客户端(而不是消息流)是这种情况下的主要瓶颈。

                                                                                                                                                                                                                                              What's left is to de­term­ine how the Ag­greg­ator will know when to send a mes­sage it has been ag­greg­at­ing. The pat­tern de­scribes a few al­gorithms for the Ag­greg­ator to know when to send the mes­sage. These in­clude al­gorithms to cause the Ag­greg­ator to send out its con­tents after a cer­tain amount of time has elapsed, after all re­quired fields in a data set have been com­pleted, and others. The prob­lem with all of these ap­proaches is that the Ag­greg­ator, not the client, is con­trolling the mes­sage flow­and the client, not the mes­sage flow, is the major bot­tle­neck in this case.

                                                                                                                                                                                                                                              这是因为聚合器假设其清除消息的使用者(在本例中为客户端应用程序)是事件驱动的使用者,或者依赖于外部源事件的使用者。我们需要将客户端变成轮询消费者,或者连续检查消息的消费者,因此客户端应用程序可以控制消息流。我们可以通过创建一个后台线程来实现这一点,该线程不断循环遍历键集并更新并闪烁自上次迭代以来发生的任何更改。通过这种方式,客户端可以控制何时接收消息,从而保证在高更新期间永远不会出现消息过载的情况。我们可以通过聚合器发送命令消息并启动更新来轻松实现这一点。聚合器将使用包含客户端将处理的更新字段集的文档消息进行响应。

                                                                                                                                                                                                                                              This is be­cause the Ag­greg­ator is as­sum­ing the con­sumers of its purged mes­sages (the client ap­plic­a­tion in this case) are Event-Driven Con­sumers, or con­sumers that rely on events from an ex­ternal source. We need to turn the client into a Polling Con­sumer, or a con­sumer that con­tinu­ously checks for mes­sages, so the client ap­plic­a­tion can con­trol the mes­sage flow. We can do this by cre­at­ing a back­ground thread that con­tinu­ously cycles through the set of bonds and up­dates and flashes any changes that have oc­curred since the last it­er­a­tion. This way, the client con­trols when mes­sages are re­ceived, and as a result, guar­an­tees that it will never become over­loaded with mes­sages during high update peri­ods. We can easily im­ple­ment this by send­ing a Com­mand Mes­sage to the Ag­greg­ator, ini­ti­at­ing an update. The Ag­greg­ator will re­spond with a Doc­u­ment Mes­sage con­tain­ing the set of up­dated fields that the client will pro­cess.

                                                                                                                                                                                                                                              选择聚合器而不是消息过滤器显然完全基于我们系统的业务需求的决定。每一个都可以帮助我们解决性能问题,但是使用消息过滤器会以牺牲系统数据完整性为代价来解决问题。

                                                                                                                                                                                                                                              The choice of Ag­greg­ator over Mes­sage Filter is clearly a de­cision based solely on the busi­ness re­quire­ments of our system. Each could help us solve our per­form­ance prob­lems, but using the Mes­sage Filter would solve the prob­lem at cost of the system data in­teg­rity.

                                                                                                                                                                                                                                              重大生产事故

                                                                                                                                                                                                                                              Major Pro­duc­tion Crash

                                                                                                                                                                                                                                              闪光灯性能确定后,我们现已投入生产。有一天,整个系统瘫痪了。MQSeries 崩溃,导致多个组件崩溃。我们与这个问题纠缠了一段时间,最后追溯到 MQSeries 死信队列(死信通道的一种实现。队列变得如此之大以至于导致整个服务器瘫痪。经过探索死信队列中的消息,我们发现它们都是过期的市场数据消息。这是由“缓慢的消费者”或处理消息的速度不够快的消费者引起的。当消息等待处理时,它们会超时(请参阅消息过期模式)并被发送到死信通道。死信队列中过期的市场数据消息数量过多清楚地表明消息流太大,消息在目标应用程序可以使用它们之前就过期了。我们需要修复消息流,并求助于模式来减缓消息流。

                                                                                                                                                                                                                                              With the per­form­ance of the flash­ing fixed, we are now in pro­duc­tion. One day, the entire system goes down. MQSer­ies crashes, bring­ing sev­eral com­pon­ents down with it. We struggle with the prob­lem for a while and fi­nally trace it back to the MQSer­ies dead letter queue (an im­ple­ment­a­tion of the Dead Letter Chan­nel). The queue grows so large that it brings down the entire server. After ex­plor­ing the mes­sages in the dead letter queue, we find they are all ex­pired market data mes­sages. This is caused by "slow con­sumers," or con­sumers that do not pro­cess mes­sages fast enough. While mes­sages are wait­ing to be pro­cessed, they time out (see the Mes­sage Ex­pir­a­tion pat­tern) and are sent to the Dead Letter Chan­nel. The ex­cess­ive number of ex­pired market data mes­sages in the dead letter queue is a clear in­dic­a­tion that the mes­sage flow is too great­mes­sages expire before the target ap­plic­a­tion can con­sume them. We need to fix the mes­sage flow, and we turn to pat­terns for help in slow­ing down the mes­sage flow.

                                                                                                                                                                                                                                              瓶颈

                                                                                                                                                                                                                                              The Bot­tle­neck

                                                                                                                                                                                                                                              图形/13inf18.gif

                                                                                                                                                                                                                                              合理的第一步是探索使用聚合器解决这个问题,因为我们最近使用这种模式来解决类似的闪烁市场数据控制率问题。系统设计依靠客户端应用程序将市场数据更新消息立即转发到交易场所。这意味着系统迫不及待地收集消息并聚合它们,因此必须放弃 Aggregator。

                                                                                                                                                                                                                                              A reas­on­able first step is to ex­plore solv­ing this prob­lem with the Ag­greg­ator, as we re­cently used this pat­tern to solve the sim­ilar flash­ing market data­con­trol rate prob­lem. The system design relies on the client ap­plic­a­tion to im­me­di­ately for­ward market data update mes­sages to the trad­ing venues. This means the system cannot wait to col­lect mes­sages and ag­greg­ate them, so the Ag­greg­ator must be aban­doned.

                                                                                                                                                                                                                                              还有另外两种模式可以处理并发消费消息的问题:竞争消费者消息调度程序。从竞争消费者开始,这种模式的好处是传入消息的并行处理。这是通过使用同一通道上的多个消费者来完成的。只有一个消费者处理每条传入消息,让其他消费者处理连续的消息。然而,竞争消费者对我们不起作用,因为我们在服务器到客户端的通信中使用发布-订阅通道。发布-订阅通道上的竞争消费者意味着所有消费者处理相同的传入消息。这会导致更多的工作却没有任何收获,并且完全达不到该模式的目标。这种做法也必须被放弃。

                                                                                                                                                                                                                                              There are two other pat­terns that deal with the prob­lem of con­sum­ing mes­sages con­cur­rently: Com­pet­ing Con­sumers and Mes­sage Dis­patcher. Start­ing with Com­pet­ing Con­sumers, the be­ne­fit of this pat­tern is the par­al­lel pro­cess­ing of in­com­ing mes­sages. This is ac­com­plished using sev­eral con­sumers on the same chan­nel. Only one con­sumer pro­cesses each in­com­ing mes­sage, leav­ing the others to pro­cess suc­cess­ive mes­sages. Com­pet­ing Con­sumers, how­ever, will not work for us, since we are using Pub­lish-Sub­scribe Chan­nels in server-to-client com­mu­nic­a­tion. Com­pet­ing Con­sumers on a Pub­lish-Sub­scribe Chan­nel means that all con­sumers pro­cess the same in­com­ing mes­sage. This res­ults in more work without any gain and com­pletely misses the goal of the pat­tern. This ap­proach also has to be aban­doned.

                                                                                                                                                                                                                                              消息调度程序描述了一种将多个执行者添加到池中的方法。每个执行者都可以运行自己的执行线程。一个主要消费者侦听消息通道并将消息委托给池中未占用的执行者,然后立即返回侦听通道。这实现了竞争消费者的并行处理优势,但适用于发布-订阅通道

                                                                                                                                                                                                                                              The Mes­sage Dis­patcher de­scribes an ap­proach whereby you add sev­eral per­formers to a pool. Each per­former can run its own ex­e­cu­tion thread. One main con­sumer listens to the Mes­sage Chan­nel and del­eg­ates the mes­sage on to an un­oc­cu­pied per­former in the pool, then im­me­di­ately re­turns to listen­ing on the chan­nel. This achieves the par­al­lel pro­cess­ing be­ne­fit of Com­pet­ing Con­sumers but works on Pub­lish-Sub­scribe Chan­nels.

                                                                                                                                                                                                                                              上下文中的消息调度程序

                                                                                                                                                                                                                                              The Mes­sage Dis­patcher in Con­text

                                                                                                                                                                                                                                              图形/13inf19.gif

                                                                                                                                                                                                                                              在我们的系统中实现这一点很简单。我们创建一个名为Dispatcher的MessageListener 其中包含名为Performers的集合。 当调用Dispatcher的onMessage方法时,它会依次集合来实际处理消息。结果是一个始终立即返回的消息侦听器( Dispatcher) 。这保证了消息处理的稳定流,而不管消息流速率如何。这在发布-订阅通道上的效果与在发布-订阅通道上的效果一样好。点对点通道。有了这个基础设施,客户端应用程序几乎可以以任何速率接收消息。如果客户端应用程序在收到消息后处理消息的速度仍然很慢,则客户端应用程序可以处理延迟处理和可能过时的市场数据,而不是处理 JMS 消息通道中过期的消息。

                                                                                                                                                                                                                                              Im­ple­ment­ing this in our system is simple. We create a single Mes­sageL­istener called the Dis­patcher, which con­tains a col­lec­tion of other Mes­sageL­isteners called Per­formers. When the on­Mes­sage method of the Dis­patcher is called, it in turn picks a Per­former out of the col­lec­tion to ac­tu­ally pro­cess the mes­sage. The result is a mes­sage listener (the Dis­patcher) that always re­turns im­me­di­ately. This guar­an­tees a steady flow of mes­sage pro­cess­ing re­gard­less of the mes­sage flow rate. This works equally as well on a Pub­lish-Sub­scribe Chan­nel as it does on a Point-to-Point Chan­nel. With this in­fra­struc­ture, mes­sages can be re­ceived by the client ap­plic­a­tion at almost any rate. If the client ap­plic­a­tion is still slow to pro­cess mes­sages after re­ceiv­ing them, the client ap­plic­a­tion can deal with the delayed pro­cess­ing and po­ten­tially out­dated market data rather than the mes­sages ex­pir­ing in the JMS Mes­sage Chan­nel.

                                                                                                                                                                                                                                              本节讨论的崩溃和使用消息调度程序的修复是应用模式限制的一个很好的例子。我们遇到了基于设计缺陷的性能问题,该缺陷阻止客户端并行处理消息。应用模式大大减少了问题,但并没有完全解决它,因为真正的问题是客户端成为瓶颈。这不可能用一千种模式来解决。后来我们通过重构消息流架构来将消息直接从定价网关路由到贡献网关来解决这个问题。因此,模式可以帮助设计和维护系统,但它们不一定能弥补糟糕的前期设计。

                                                                                                                                                                                                                                              The crash dis­cussed in this sec­tion and the fix using the Mes­sage Dis­patcher are an ex­cel­lent ex­ample of the limits of ap­ply­ing pat­terns. We en­countered a per­form­ance prob­lem based on a design flaw that pre­ven­ted the client from pro­cess­ing mes­sages in par­al­lel. Ap­ply­ing pat­terns greatly re­duced the prob­lem but did not com­pletely fix it, be­cause the real prob­lem was the client be­com­ing a bot­tle­neck. This couldn't be fixed with a thou­sand pat­terns. We later ad­dressed this prob­lem by re­fact­or­ing the mes­sage flow ar­chi­tec­ture to route mes­sages dir­ectly from the pri­cing gate­way to the con­tri­bu­tion gate­way. So, pat­terns can help design and main­tain a system, but they don't ne­ces­sar­ily make up for poor up­front design.

                                                                                                                                                                                                                                              概括

                                                                                                                                                                                                                                              Sum­mary

                                                                                                                                                                                                                                              在本章中,我们将模式应用于债券交易系统的几个不同方面,包括解决最初的前期设计问题以及使用模式修复几乎威胁工作的生产崩溃。我们还看到了这些模式,因为它们已经存在于第三方产品、遗留组件以及我们的 JMS 和 TIBCO 消息传递系统中。最重要的是,这些都是我们在设计和维护自己的系统时遇到的相同类型的架构、技术和业务问题的实际问题。希望阅读有关将模式应用于该系统的内容能让您更好地理解这些模式以及如何将它们应用到您自己的系统中。

                                                                                                                                                                                                                                              Through­out this chapter, we have ap­plied pat­terns to sev­eral dif­fer­ent as­pects of a bond trad­ing system, in­clud­ing solv­ing ini­tial up­front design prob­lems and fixing a nearly job-threat­en­ing pro­duc­tion crash with pat­terns. We also saw these pat­terns as they already exist in third-party products, legacy com­pon­ents, and our JMS and TIBCO mes­saging sys­tems. Most im­port­antly, these are real prob­lems with the same types of ar­chi­tec­tural, tech­nical, and busi­ness prob­lems we ex­per­i­ence as we design and main­tain our own sys­tems. Hope­fully, read­ing about ap­ply­ing pat­terns to this system has given you a better un­der­stand­ing of the pat­terns and how to apply them to your own sys­tems.

                                                                                                                                                                                                                                                企业集成的新兴标准和未来

                                                                                                                                                                                                                                                Emerging Standards and Futures in Enterprise Integration

                                                                                                                                                                                                                                                通过肖恩·内维尔

                                                                                                                                                                                                                                                by Sean Neville

                                                                                                                                                                                                                                                随着数据通过消息传递管道跨系统和域边界流动,并且随着开发人员和架构师变得更加精通管理消息传递系统的模式,新的标准和产品将会出现,以扩展这些模式的战术范围。随着时间的推移,模​​式往往会加强,但在其他方面变化甚微(如果有的话)。但它们的实施策略通常会迅速发展,以允许开发人员将它们应用到更广泛的复杂范围。基本信息例如,随着实现工件从电子数据交换 (EDI) 发展到专有的面向消息的中间件 (MOM)、开放 XML 和基于 SOAP 的 Web 服务、全球业务流程执行语言 (BPEL) 和超过。

                                                                                                                                                                                                                                                As data flows across system and domain bound­ar­ies through mes­saging con­duits, and as de­ve­lopers and ar­chi­tects become more pro­fi­cient in the pat­terns that govern mes­saging sys­tems, new stand­ards and products will emerge to extend the tac­tical reach of those pat­terns. Over time, pat­terns tend to strengthen but oth­er­wise change little, if at all; but their im­ple­ment­a­tion strategies often evolve rap­idly to allow de­ve­lopers to apply them to much broader scales of soph­ist­ic­a­tion. The fun­da­mental Mes­sage pat­tern, for ex­ample, finds its reach and ap­plic­ab­il­ity ex­ten­ded as im­ple­ment­a­tion ar­ti­facts grow from Elec­tronic Data In­ter­change (EDI) to pro­pri­et­ary Mes­sage-Ori­ented Mid­dle­ware (MOM) to open XML and SOAP-based Web ser­vices to global Busi­ness Pro­cess Ex­e­cu­tion Lan­guage (BPEL) and beyond.

                                                                                                                                                                                                                                                本章根据应用程序开发人员在 2000 年代中期将遇到的新兴标准来展望基于消息的企业集成的未来。其中许多标准目前尚未普遍使用,但正在与广泛的行业支持相结合,并且可能成为集成模式实现的基础,特别是在面向服务的体系结构中。其中大多数扩展了新兴的 Web 服务技术来支持本书中介绍的模式类型。我们研究为什么这些标准对于设计模式很重要,哪些组织正在创建它们以及他们是如何进行的,

                                                                                                                                                                                                                                                This chapter takes a look at the future of mes­sage-based en­ter­prise in­teg­ra­tion in terms of the emer­ging stand­ards that ap­plic­a­tion de­ve­lopers will en­counter in the mid-2000s. Many of these stand­ards are not cur­rently in common use but are co­ales­cing with broad in­dustry sup­port and are likely to serve as the found­a­tion of in­teg­ra­tion pat­tern im­ple­ment­a­tions, par­tic­u­larly in ser­vice-ori­ented ar­chi­tec­tures. Most of them extend nas­cent Web ser­vices tech­no­lo­gies to sup­port the types of pat­terns presen­ted in this book. We look at why these stand­ards are im­port­ant in re­la­tion to design pat­terns, which or­gan­iz­a­tions are cre­at­ing them and how they're going about it, and offer a brief sum­mary of the tech­nical solu­tions for busi­ness pro­cess in­teg­ra­tion that a few of the most prom­ising Web ser­vices and Java stand­ards aim to provide.

                                                                                                                                                                                                                                                标准和设计模式之间的关系

                                                                                                                                                                                                                                                The Re­la­tion­ship between Stand­ards and Design Pat­terns

                                                                                                                                                                                                                                                两种思维产物提供了当今软件架构中最高级别的抽象:面向编程,包括面向对象编程、面向服务编程、生成式编程等;和模式语言,如本书中记录的那样。如果一个特定的方向或设计模式被证明是有用的,并且如果它的上下文被证明经常重复出现,那么用于实现该模式的策略和策略通常会变得非常相似。多个平台、产品和应用程序上的模式解决方案最终拥有很少的差异,但这些微小的差异通常是令人沮丧的增长抑制剂,通常是语义性的,并产生阻碍规模复杂性的互操作性。

                                                                                                                                                                                                                                                Two mental ar­ti­facts provide the highest levels of ab­strac­tion in soft­ware ar­chi­tec­ture today: pro­gram­ming ori­ent­a­tions, which in­clude object-ori­ented pro­gram­ming, ser­vice-ori­ented pro­gram­ming, gen­er­at­ive pro­gram­ming, and the like; and pat­tern lan­guages, such as that doc­u­mented in this book. If a par­tic­u­lar ori­ent­a­tion or design pat­tern proves useful, and if its con­text proves to recur fre­quently, the tac­tics and strategies used to im­ple­ment the pat­tern often become very sim­ilar. Pat­tern solu­tions on mul­tiple plat­forms, products, and ap­plic­a­tions end up owning very few dif­fer­ences­but those few dif­fer­ences are often frus­trat­ing growth in­hib­it­ors, typ­ic­ally se­mantic in nature, and pro­duce in­ter­op­er­ab­il­ity that hinders soph­ist­ic­a­tion of scale. To extend the reach of ap­plic­a­tions and the pat­terns on which they're based, such dif­fer­ences tend to be elim­in­ated from products and plat­forms through form­ally agreed-upon stand­ards.

                                                                                                                                                                                                                                                当模式的策略或实现策略被标准化时,该模式的实用性并不会减弱,而是会被该标准所取代;相反,情况恰恰相反。就像树干内的年轮一样,强大的模式会自行发展到更广泛的适用性水平,在更广泛的背景实例中创建并应用自己。这种规模的增长是因为模式实现的不同实例可以彼此互操作。以典型的面向消息的J2EE应用程序为例,管道和过滤器等模式存在于开发人员设计的应用程序内的多个级别,以及用于托管应用程序的服务器产品内、该服务器内的容器和服务内、构成消息传递子系统的过滤器和组件内,等等以递归方式存在。

                                                                                                                                                                                                                                                When a pat­tern's tactic or im­ple­ment­a­tion strategy is stand­ard­ized, the pat­tern does not wane in use­ful­ness, re­placed by that stand­ard; in­stead, quite the op­pos­ite occurs. Like rings within a tree trunk, a strong pat­tern grows upon itself to ever-broader levels of ap­plic­ab­il­ity, cre­at­ing and then ap­ply­ing itself in broader in­stances of con­text. This growth in scale occurs be­cause sep­ar­ate in­stances of a pat­tern's im­ple­ment­a­tion become in­ter­op­er­able with one an­other. Taking a typ­ical mes­sage-ori­ented J2EE ap­plic­a­tion as an ex­ample, a pat­tern such as Pipes and Fil­ters exists at many levelswithin the ap­plic­a­tion as de­signed by the de­ve­loper and also within the server product used to host the ap­plic­a­tion, within the con­tain­ers and ser­vices within that server, within the fil­ters and com­pon­ents that com­pose the mes­saging sub­sys­tems, and so forth in re­curs­ive fash­ion.

                                                                                                                                                                                                                                                新兴的消息传递和 Web 服务标准将通过扩展使用它们的开发人员的范围来增强本书中的模式。BPEL 和 Web 服务可靠性 (WS-Reliability) 等标准将这些模式的范围扩展到比特、语言、产品之外,并向外传播到以人类和系统为中心的用例和需求圈中越来越有用的组合。应用程序开发人员可以停止使用相关标识符等模式将原始消息回复与其发送者相匹配,并可以开始使用相同的模式来关联由多个异步消息发送者和接收者组成的流程组件。如果没有消息传递标准的好处,Java 开发人员可能会在代码级别实现消息路由器来检查各个 XML 消息的内容,以便以编程方式将消息转发到特定服务或过滤器。新兴的工作流程和编排标准将允许同一开发人员应用他或她的消息路由器知识模式在更高的抽象级别上在流程组合之间路由工作流,其中战术工件是业务流程组件而不是 XML 文档的原始部分。换句话说,应用程序开发人员可以停止花费宝贵的时间链接协议,并着手跨域边界链接业务流程和服务。

                                                                                                                                                                                                                                                Emer­ging mes­saging and Web ser­vice stand­ards will serve to strengthen the pat­terns in this book by ex­tend­ing the reach of the de­ve­lopers who employ them. Stand­ards such as BPEL and Web Ser­vices Re­li­ab­il­ity (WS-Re­li­ab­il­ity) propag­ate the scope of these pat­terns beyond bits, beyond lan­guages, beyond products, and out­ward into in­creas­ingly useful com­pos­i­tions in the human- and system-cent­ric circles of use cases and re­quire­ments. Ap­plic­a­tion de­ve­lopers can stop using pat­terns such as Cor­rel­a­tion Iden­ti­fier to match raw mes­sage replies to their senders and can start using that same pat­tern to cor­rel­ate pro­cess com­pon­ents that con­sist of mul­tiple asyn­chron­ous mes­sage senders and re­ceiv­ers. Without the be­ne­fit of mes­saging stand­ards, a Java de­ve­loper may im­ple­ment Mes­sage Router at the code level to in­spect the con­tents of in­di­vidual XML mes­sages in order to pro­gram­mat­ic­ally for­ward the mes­sage to a par­tic­u­lar ser­vice or filter. Emer­ging work­flow and cho­reo­graphy stand­ards will allow that same de­ve­loper to apply his or her know­ledge of the Mes­sage Router pat­tern at a higher level of ab­strac­tion to route work­flow among pro­cess com­pos­i­tions, where the tac­tical ar­ti­facts are busi­ness pro­cess com­pon­ents rather than raw sec­tions of XML doc­u­ments. In other words, ap­plic­a­tion de­ve­lopers can stop spend­ing pre­cious time link­ing pro­to­cols and set about the busi­ness of link­ing busi­ness pro­cesses and ser­vices across domain bound­ar­ies.

                                                                                                                                                                                                                                                设计模式和编程方向对于开发复杂系统的有用性与应用程序开发人员依赖通用的、可互操作的实施策略和战术的能力成正比。如今,在 J2EE 中使用消息传递模式的策略可能意味着使用 JMS、JCA 或 JAX-RPC;明天,应用程序开发人员使用的策略可能涉及使用模型驱动的技术、基于模式的脚本、方面或意图,并且如果适当地接受了模式的上下文定义和相关标准,则实现策略中的任何此类转变只会增加模式的重要性。

                                                                                                                                                                                                                                                The use­ful­ness of design pat­terns and pro­gram­ming ori­ent­a­tion for de­vel­op­ing soph­ist­ic­ated sys­tems is pro­por­tional to the ap­plic­a­tion de­ve­loper's abil­ity to depend upon common, in­ter­op­er­able im­ple­ment­a­tion strategies and tac­tics. Today, strategies for work­ing with a mes­saging pat­tern in J2EE may mean use of JMS, JCA, or JAX-RPC; to­mor­row, the strategies used by ap­plic­a­tion de­ve­lopers may in­volve use of model-driven tech­no­logy, schema-based scripts, as­pects, or in­ten­tion­sand any such shifts in im­ple­ment­a­tion tac­tics only in­crease the sig­ni­fic­ance of the pat­terns if the pat­tern's con­tex­tual defin­i­tion and rel­ev­ant stand­ards are ap­pro­pri­ately em­braced. Stand­ards provide the best way we cur­rently know to en­force these com­mon­al­it­ies to extend the soft­ware ar­chi­tect's mas­tery of design pat­terns.

                                                                                                                                                                                                                                                标准流程和组织调查

                                                                                                                                                                                                                                                Survey of Stand­ards Pro­cesses and Or­gan­iz­a­tions

                                                                                                                                                                                                                                                与流行的愤世嫉俗相反,标准并不是在与实际应用程序开发隔离的供应商真空中开发的。它们旨在统一和改进应用程序开发人员发现和开发的实现方法。通过练习本书中的设计模式,即使您不直接为规范工作组做出贡献,也可能会为标准的开发做出贡献。监督规范工作组的组织和联盟并不总是在识别和同化实施方法方面做得很好,但这就是目的。

                                                                                                                                                                                                                                                Con­trary to pop­u­lar cyn­icism, stand­ards are not in­ten­ded to be de­ve­loped in a vendor vacuum isol­ated from prac­tical ap­plic­a­tion de­vel­op­ment. They are in­ten­ded to unify and im­prove the im­ple­ment­a­tion ap­proaches dis­covered and de­ve­loped by ap­plic­a­tion de­ve­lopers. By ex­er­cising the design pat­terns in this book, you may be con­trib­ut­ing to the de­vel­op­ment of a stand­ard even if you don't dir­ectly con­trib­ute to a spe­cific­a­tion work­ing group. The or­gan­iz­a­tions and con­sor­ti­ums that over­see spe­cific­a­tion work­ing groups don't always do a great job of re­cog­niz­ing and as­sim­il­at­ing im­ple­ment­a­tion ap­proaches, but that's the in­ten­tion.

                                                                                                                                                                                                                                                当一个发明者或一组发明者向标准机构提出正式提案时,标准就正式诞生。通常,这需要成为标准机构的成员。每个标准机构都执行自己的流程,将提案形成标准,所有成员都受到这些规则的法律约束。这些过程通常涉及组建一个工作组或委员会,以在组织管理层的监督和最终批准下进一步制定规范。知识产权和许可政策通常是这些组织中的热门话题,并且在标准机构之间甚至标准机构内的工作组之间可能会有所不同。

                                                                                                                                                                                                                                                Stand­ards are of­fi­cially born when an in­ventor or group of in­vent­ors makes a formal pro­posal to a stand­ards body. Usu­ally, this re­quires mem­ber­ship in the stand­ards body. Each stand­ards body en­forces its own pro­cess for shap­ing pro­pos­als into a stand­ard, and all mem­bers are leg­ally bound to those rules. The pro­cesses gen­er­ally in­volve the form­ing of a work­ing group or com­mit­tee to fur­ther de­velop the spe­cific­a­tion under the over­sight and even­tual ap­proval of the or­gan­iz­a­tion's man­age­ment. In­tel­lec­tual prop­erty rights and li­cens­ing policies are often hot topics in these or­gan­iz­a­tions and can vary between stand­ards bodies and even between work­ing groups within the stand­ards bodies.

                                                                                                                                                                                                                                                以下是参与创建新兴消息传递和 Web 服务标准的主要标准组织:

                                                                                                                                                                                                                                                Here's a look at the major stand­ards or­gan­iz­a­tions in­volved in cre­at­ing the emer­ging mes­saging and Web ser­vice stand­ards:

                                                                                                                                                                                                                                                W3C:万维网联盟( http://www.w3c.org )开发了许多基本的 Web 技术,这些技术又被其他标准组织用作构建块。它由一个由研究人员和工程师组成的国际团队管理,并由一大批成员供应商、内容提供商、政府、研究实验室和其他实体组成。W3C 在工作组中以及随后的公开中利用开放的协作审查流程,从而导致冗长但总体质量较高的迭代。通过 W3C 创建的技术通常不受成员知识产权主张的阻碍。W3C 技术包括 SOAP 和 WSDL 以及所有核心 XML 规范。

                                                                                                                                                                                                                                                OASIS:以 W3C 技术为基础的几个组织之一是结构化信息标准促进组织 (Organization for the Advancement of Structured Information Standards),即 OASIS ( http://www.oasis-open.org )。这个非营利供应商联盟旨在促进 SGML 开发指南,现在致力于推动全球电子商务标准的采用。OASIS 技术委员会正在制定许多新兴的 Web 服务标准,例如 ebXML 和 WS-Reliability。OASIS 还托管xml.org门户。

                                                                                                                                                                                                                                                WS-I:Web 服务互操作性组织 (WS-I) ( http://www.ws-i.org )旨在确保 Web 服务技术和标准适合以通用、可互操作的方式实现业务协作。该组织推广可跨多个系统、平台和语言应用的 Web 服务协议和实践。实现这些目标的一个关键策略是 WS-I Basic Profile,它在第一个配置文件中指定了 Web 服务标准及其版本号的集合,这些标准是 XML Schema 1.0、SOAP 1.1、WSDL 1.1 和 UDDI 1.0 以及约定规定它们应该如何一起使用。因此,WS-I 打算发挥统一作用,确保参与 Web 服务开发的各个供应商能够以有利于应用程序开发人员的方式协同工作。

                                                                                                                                                                                                                                                JCP:Java Community Process (JCP) ( http://www.jcp.org )为其他组织开发的 Web 服务和消息传递标准等技术生成 Java 语言绑定和 J2EE API。由 Sun Microsystems 领导的 JCP 历史上并不是一个开放的过程;尽管它使用与其他标准组织类似的专家组和提案流程,但知识产权通常由 Sun 保留,并有偿许可给 Java 和 J2EE 平台供应商。大多数 JSR 专家组也由 Sun 工程师领导。虽然缺乏其他组织的开放性,但 JCP 受益于 Sun 提供的关注,在某种程度上不受外部议程的阻碍。

                                                                                                                                                                                                                                                特设供应商联盟:在建立对部分新兴 Web 服务技术的控制权的竞赛中,特别是为了企业集成的目的,IBM 和 Microsoft 等传统竞争对手在某些情况下联合起来发布了未来的标准,而不将这些工作提交给任何标准机构。许多 WS-* 规范都属于这一类。通常,通过供应商联盟制定的标准最终会提交给标准组织;例如,前景光明的 BPEL 在提交给 OASIS 之前,就曾是 Microsoft 和 IBM 的创造。对保留知识产权的兴趣似乎是临时工作的催化剂。这些规范无需许可开放即可实现互操作性,

                                                                                                                                                                                                                                                W3C: The World Wide Web Con­sor­tium (http://www.w3c.org) de­vel­ops many of the basic Web tech­no­lo­gies that are in turn used as build­ing blocks by other stand­ards or­gan­iz­a­tions. It is man­aged by an in­ter­na­tional team of re­search­ers and en­gin­eers, and con­sists of a large group of member vendors, con­tent pro­viders, gov­ern­ments, re­search labor­at­or­ies, and other en­tit­ies. The W3C makes use of an open, col­lab­or­at­ive review pro­cess in work­ing groups and sub­se­quently in public that res­ults in lengthy but gen­er­ally high-qual­ity it­er­a­tions. Tech­no­lo­gies cre­ated through the W3C are usu­ally un­en­cumbered by member claims to in­tel­lec­tual prop­erty rights. W3C tech­no­lo­gies in­clude SOAP and WSDL as well as all of the core XML spe­cific­a­tions. The W3C Cho­reo­graphy Work­ing Group will likely re­solve con­flict­ing busi­ness pro­cess spe­cific­a­tions and provide the defin­it­ive basis for in­teg­ra­tion using Web ser­vices.

                                                                                                                                                                                                                                                OASIS: One of sev­eral or­gan­iz­a­tions that builds on W3C tech­no­lo­gies is the Or­gan­iz­a­tion for the Ad­vance­ment of Struc­tured In­form­a­tion Stand­ards, or OASIS (http://www.oasis-open.org). Chartered to foster guidelines for SGML de­vel­op­ment, this non­profit con­sor­tium of vendors now fo­cuses on driv­ing the ad­op­tion of global e-busi­ness stand­ards. OASIS tech­nical com­mit­tees are pro­du­cing many emer­ging Web ser­vice stand­ards, such as ebXML and WS-Re­li­ab­il­ity. OASIS also hosts the xml.org portal.

                                                                                                                                                                                                                                                WS-I: The Web Ser­vices In­ter­op­er­ab­il­ity Or­gan­iz­a­tion (WS-I) (http://www.ws-i.org) aims to ensure that Web ser­vice tech­no­lo­gies and stand­ards are suit­able for en­abling busi­ness col­lab­or­a­tions in a gen­eric, in­ter­op­er­able manner. The or­gan­iz­a­tion pro­motes Web ser­vice pro­to­cols and prac­tices that can apply across mul­tiple sys­tems, plat­forms, and lan­guages. A key tactic in achiev­ing these goals is the WS-I Basic Pro­file, which spe­cifies the col­lec­tion of Web ser­vice stand­ards along with their ver­sion num­ber­sin the first pro­file these are XML Schema 1.0, SOAP 1.1, WSDL 1.1, and UDDI 1.0as well as con­ven­tions gov­ern­ing how they should be used to­gether. Thus, the WS-I in­tends to play a uni­fy­ing role in en­sur­ing that vari­ous vendors in­volved in Web ser­vices de­vel­op­ment can work to­gether in a way that be­ne­fits ap­plic­a­tion de­ve­lopers. It was foun­ded and is man­aged by the lead­ing Web ser­vices vendor com­pan­ies, in­clud­ing Mi­crosoft, IBM, BEA Sys­tems, Oracle, and others.

                                                                                                                                                                                                                                                JCP: The Java Com­munity Pro­cess (JCP) (http://www.jcp.org) pro­duces Java lan­guage bind­ings and J2EE APIs for tech­no­lo­gies such as the Web ser­vices and mes­saging stand­ards de­ve­loped by other or­gan­iz­a­tions. Led by Sun Mi­crosys­tems, the JCP has not his­tor­ic­ally been an open pro­cess; al­though it makes use of expert groups and a pro­posal pro­cess sim­ilar to that of other stand­ards or­gan­iz­a­tions, in­tel­lec­tual prop­erty rights are typ­ic­ally re­tained by Sun and li­censed to the Java and J2EE plat­form vendors for a fee. Most of the JSR expert groups are also led by Sun en­gin­eers. While lack­ing the open­ness of other or­gan­iz­a­tions, the JCP has be­nefited from the focus that Sun has provided, some­what un­hindered by ex­ternal agen­das.

                                                                                                                                                                                                                                                Ad Hoc Vendor Con­sor­ti­ums: In the race to es­tab­lish con­trol over por­tions of the emer­ging Web ser­vices tech­no­lo­gies, par­tic­u­larly for the pur­poses of en­ter­prise in­teg­ra­tion, tra­di­tional com­pet­it­ors such as IBM and Mi­crosoft have in some cases united to pub­lish would-be stand­ards without sub­mit­ting those works to any stand­ards body. Many of the WS-* spe­cific­a­tions fall into this bucket. Often, stand­ards de­ve­loped through vendor con­sor­ti­ums end up as sub­mis­sions to stand­ards or­gan­iz­a­tions; the prom­ising BPEL, for ex­ample, spent its early life as a cre­ation of Mi­crosoft and IBM before being sub­mit­ted to OASIS. In­terest in re­tain­ing in­tel­lec­tual prop­erty rights seems to be the cata­lyst for work­ing in ad hoc fash­ion. These spe­cific­a­tions achieve in­ter­op­er­ab­il­ity without the li­cens­ing open­ness, which is often sat­is­fact­ory for ap­plic­a­tion de­ve­lopers in­teg­rat­ing plat­forms as pop­u­lar as those pro­duced by Mi­crosoft and IBM, though whether these works are truly "open stand­ards" is un­der­stand­ably the sub­ject of much debate.

                                                                                                                                                                                                                                                业务流程组件和 Web 服务内消息传递

                                                                                                                                                                                                                                                Busi­ness Pro­cess Com­pon­ents and Intra-Web Ser­vice Mes­saging

                                                                                                                                                                                                                                                亚里士多德不仅满足于影响面向对象的编程、结构分解和领域建模,还顽皮地提出了困扰软件架构的最具影响力的反问之一:世界主要是一系列过程,还是一系列对象?

                                                                                                                                                                                                                                                Not con­tent merely to in­flu­ence object-ori­ented pro­gram­ming, struc­tural de­com­pos­i­tion, and domain mod­el­ing, Ar­is­totle also mis­chiev­ously posed one of the most in­flu­en­tial rhet­or­ical ques­tions ever to trouble soft­ware ar­chi­tec­ture: Is the world pre­dom­in­antly a series of pro­cesses, or is it a series of ob­jects?

                                                                                                                                                                                                                                                消息传递标准给出了一个禅宗弟子的答案:是的。世界是一系列对象,其最重要的特征通常是它们与其他对象交互和关联的过程。事实证明,最重要的往往是一个对象与其他对象相关的行为,而不是它自己的内部组成。在 Web 服务和企业集成领域,这种对象/流程混合视图称为业务流程组件。业务流程组件将一系列服务联合成一个逻辑单元,该逻辑单元通过消息传递与其他此类单元交互,以实现高度可扩展、有弹性的逻辑和数据流。过程、对象的结合,

                                                                                                                                                                                                                                                Mes­saging stand­ards reply with an answer worthy of a Zen dis­ciple: yes. The world is a series of ob­jects whose most im­port­ant char­ac­ter­ist­ics are usu­ally the pro­cesses through which they engage with and relate to other ob­jects. It is often an object's be­ha­vi­ors re­lated to other ob­jects rather than its own in­ternal com­pos­i­tion that turn out to be most sig­ni­fic­ant. In the realm of Web ser­vices and en­ter­prise in­teg­ra­tion, this object/pro­cess hybrid view is re­ferred to as the busi­ness pro­cess com­pon­ent. The busi­ness pro­cess com­pon­ent unites a series of ser­vices into a lo­gical unit that in­ter­acts with other such units via mes­saging to achieve highly scal­able, re­si­li­ent flows of logic and data. The co­ales­cence of pro­cess, object, and in­ter­ac­tion pat­terns into a busi­ness pro­cess com­pon­ent is the future of mes­saging as seen by many Web ser­vices vendors and stand­ards bodies.

                                                                                                                                                                                                                                                流程组件是与企业集成特别相关的一组服务的宏观视图。举个非技术性的例子,人可以驾驶汽车,而人和汽车都是独立的、高度复杂的系统;然而,人类和汽车的过程视图将它们视为由彼此相互作用定义的单个组件,并且不关注与它们之间的相互作用分开的人类或汽车的组成。此外,流程组件视图描述了该单个组件与道路上其他组件的交互,同样对其他汽车或驾驶员的内部组成不感兴趣。这与简单地呈现外部接口不同,

                                                                                                                                                                                                                                                The pro­cess com­pon­ent is a macro view of a set of ser­vices that is par­tic­u­larly rel­ev­ant to en­ter­prise in­teg­ra­tion. Taking a non­tech­nical ex­ample, a human can drive a car, and both the human and the car are sep­ar­ate, highly-com­plex sys­tems; the pro­cess view of the human and car, how­ever, sees them as a single com­pon­ent defined by their in­ter­ac­tions with one an­other and does not focus on the com­pos­i­tion of either the human or the car sep­ar­ate from the in­ter­ac­tion between them. Fur­ther, the pro­cess com­pon­ent view de­scribes the in­ter­ac­tions of that single com­pon­ent with others on the road, again without in­terest in the in­ternal com­pos­i­tion of other auto­mo­biles or drivers. This is not the same as simply present­ing an ex­ternal in­ter­face, as it also in­cludes the rules that govern be­ha­vi­ors between usages of the in­ter­faces.

                                                                                                                                                                                                                                                标准中出现的业务流程组件是一个单一组件,其内部由一组 Web 服务以及消息如何流入和流出这些服务的定义组成。Web 服务和其他消息目标是用于组成更大组合的构建块,这些组合的交互遵循消息传递模式,流程组件的组合以及多个流程组件到应用程序的链接都遵循消息传递模式。业务流程标准解决 Web 服务之间的消息关联问题,以便创建执行流并包括与错误、事务和数据交换相关的行为。

                                                                                                                                                                                                                                                The busi­ness pro­cess com­pon­ent, as emer­ging in stand­ards, is a single com­pon­ent that in­tern­ally con­sists of a set of Web ser­vices and a defin­i­tion for how mes­sages flow into and out of them. Web ser­vices and other mes­sage des­tin­a­tions are build­ing blocks used to com­pose larger com­pos­i­tions whose in­ter­ac­tions follow mes­saging pat­terns­both the com­pos­i­tion of the pro­cess com­pon­ent and the link­ing of mul­tiple pro­cess com­pon­ents into an ap­plic­a­tion follow mes­saging pat­terns. Busi­ness pro­cess stand­ards ad­dress the cor­rel­a­tion of mes­sages between Web ser­vices in order to create flows of ex­e­cu­tion and in­clude be­ha­vi­ors re­lated to errors, trans­ac­tions, and data ex­change.

                                                                                                                                                                                                                                                下图展示了一个更具技术性的业务示例,其中采购订单的处理由包含多个服务操作的组件来处理。

                                                                                                                                                                                                                                                A more tech­nical busi­ness ex­ample is il­lus­trated in the fol­low­ing figure in which the pro­cess­ing of a pur­chase order is handled by a com­pon­ent that con­sists of a number of ser­vice op­er­a­tions.

                                                                                                                                                                                                                                                代表多个内部编排的 Web 服务公开单个目标端点的业务流程组件

                                                                                                                                                                                                                                                The Busi­ness Pro­cess Com­pon­ent Ex­pos­ing a Single Des­tin­a­tion En­d­point on Behalf of Mul­tiple In­tern­ally Cho­reo­graphed Web Ser­vices

                                                                                                                                                                                                                                                图形/14inf01.gif

                                                                                                                                                                                                                                                该示例包括以下特征和活动:

                                                                                                                                                                                                                                                The ex­ample in­cludes the fol­low­ing char­ac­ter­ist­ics and activ­it­ies:

                                                                                                                                                                                                                                                • 处理采购订单组件中的每个操作都是在Web 服务的portType上可用的操作,并且公开这些操作的各种 Web 服务可能驻留在合作伙伴生产设施的远程主机上。

                                                                                                                                                                                                                                                • Each op­er­a­tion in the Pro­cess Pur­chase Order com­pon­ent is an op­er­a­tion avail­able on the port­Type of a Web ser­vice, and the vari­ous Web ser­vices that expose the op­er­a­tions may reside on remote hosts in part­ner pro­duc­tion fa­cil­it­ies.

                                                                                                                                                                                                                                                • 当收到采购订单时,会同时调用四个操作,但其中一些操作具有必须同步解决的依赖关系(图中的虚线箭头表示依赖关系)。

                                                                                                                                                                                                                                                • When a pur­chase order is re­ceived, four op­er­a­tions are in­voked con­cur­rently, but some of the op­er­a­tions have de­pend­en­cies that must be syn­chron­ously re­solved (the dashed arrows in the il­lus­tra­tion in­dic­ate de­pend­en­cies).

                                                                                                                                                                                                                                                • 两个特定的依赖关系表明,为了计算最终成本,需要运输协议和保险费用,并且在制造商将资源投入生产计划之前必须收到具有约束力的保险协议。

                                                                                                                                                                                                                                                • Two spe­cific de­pend­en­cies show that the ship­ping agree­ment and in­sur­ance costs are needed in order to cal­cu­late the final cost and that a bind­ing in­sur­ance agree­ment must be re­ceived before the man­u­fac­turer will commit re­sources to a pro­duc­tion sched­ule.

                                                                                                                                                                                                                                                • 所有服务操作异步完成后,采购订单就已得到处理。流程组件不要求消息传递客户端单独与所有这些服务进行交互并管理它们之间的依赖关系,也不要求客户端创建流程管理器实现,而是代表所有服务公开单个网络端点并管理所有服务内部服务之间的消息传递和依赖关系,简化了客户端的任务。

                                                                                                                                                                                                                                                • Once all ser­vice op­er­a­tions have asyn­chron­ously com­pleted, the pur­chase order has been pro­cessed. Rather than re­quir­ing the mes­saging client to in­ter­act with all of these ser­vices in­di­vidu­ally and manage the de­pend­en­cies between themthat is, rather than re­quir­ing the client to create a Pro­cess Man­ager im­ple­ment­a­tionthe pro­cess com­pon­ent ex­poses a single net­work en­d­point on behalf of all the ser­vices and man­ages all of the mes­saging and de­pend­en­cies between ser­vices in­tern­ally, sim­pli­fy­ing the client's tasks.

                                                                                                                                                                                                                                                • 采购订单流程组件又链接到另一个业务流程组件,即流程发票组件。

                                                                                                                                                                                                                                                • The Pur­chase Order pro­cess com­pon­ent is in turn linked to an­other busi­ness pro­cess com­pon­ent, the Pro­cess In­voice Com­pon­ent.

                                                                                                                                                                                                                                                四个标准倡议保证了业务流程组件和集成空间的良好外观:ebXML 倡议、称为业务流程执行语言和 Web 服务编排接口的两个竞争提案,以及通过WS-前缀连接的单个规范的集合,这些规范解决了相同的功能,但形式更窄、更具体。

                                                                                                                                                                                                                                                Four stand­ards ini­ti­at­ives war­rant a good look in the busi­ness pro­cess com­pon­ent and in­teg­ra­tion space: the ebXML ini­ti­at­ive, two com­pet­ing pro­pos­als called Busi­ness Pro­cess Ex­e­cu­tion Lan­guage and Web Ser­vices Cho­reo­graphy In­ter­face, and a col­lec­tion of in­di­vidual spe­cific­a­tions joined through the WS-prefix that ad­dress pieces of the same func­tion­al­ity in slightly nar­rower, more spe­cific fash­ion.

                                                                                                                                                                                                                                                ebXML 和电子商务消息服务 (ebMS)

                                                                                                                                                                                                                                                ebXML and the Elec­tronic Busi­ness Mes­saging Ser­vice (ebMS)

                                                                                                                                                                                                                                                甚至在 SOAP 和 WSDL 爆炸性流行之前,许多从事业务协作和 B2B 集成项目的聪明的思想家就看到需要开发一个开放、安全和可互操作的基础架构,以便使用 XML 消息传递交换业务信息。在电子商务下使用可扩展标记语言 (ebXML) 旗帜开发的几个规范和计划满足了这一需求,并且随着 SOAP 等技术的流行,ebXML 已经发展到包含这些技术并以此为基础。ebXML 计划由 OASIS 和联合国 UN/CEFACT 组织共同管理。UN/CEFACT 是将 EDI 标准引入世界的全球组织,ebXML 在许多方面代表了 EDI 发展的下一个逻辑阶段。

                                                                                                                                                                                                                                                Even before the ex­plos­ive pop­ular­ity of SOAP and WSDL, many bright thinkers work­ing on busi­ness col­lab­or­a­tion and B2B in­teg­ra­tion pro­jects saw a need to de­velop an open, secure, and in­ter­op­er­able in­fra­struc­ture for ex­chan­ging busi­ness in­form­a­tion using XML mes­saging. The sev­eral spe­cific­a­tions and ini­ti­at­ives de­ve­loped under the Elec­tronic Busi­ness using eX­tens­ible Markup Lan­guage (ebXML) banner ad­dress that need, and as tech­no­lo­gies such as SOAP have become pop­u­lar, ebXML has grown to in­clude and build upon them. The ebXML ini­ti­at­ives are jointly man­aged through OASIS and the United Na­tions UN/CEFACT or­gan­iz­a­tion. UN/CEFACT is the global group that brought the EDI stand­ard into the world, and ebXML in many ways rep­res­ents the next lo­gical stage in the evol­u­tion of EDI.

                                                                                                                                                                                                                                                ebXML 包含多个规范,涵盖的主题包括企业如何宣传其业务流程并搜索潜在合作伙伴的业务流程、合作伙伴如何达成一致并发起通信、使用注册表来促进业务对话的发现和初始化以及业务流程的行为。促进对话所需的消息传递基础设施。最后一个元素尤其引起了极大的关注,虽然它利用了其他 ebXML 规范,但也可以单独考虑它,这对于关注消息传递模式很有用。

                                                                                                                                                                                                                                                Sev­eral spe­cific­a­tions com­prise ebXML, cov­er­ing topics such as how en­ter­prises ad­vert­ise their busi­ness pro­cess and search for those of po­ten­tial part­ners, how part­ners agree upon and ini­ti­ate com­mu­nic­a­tion, the use of re­gis­tries to fa­cil­it­ate the dis­cov­ery and ini­tial­iz­a­tion of busi­ness con­ver­sa­tions, and the be­ha­vior of the mes­saging in­fra­struc­ture re­quired to fa­cil­it­ate the con­ver­sa­tions. This last ele­ment in par­tic­u­lar has garnered a great deal of at­ten­tion, and while it lever­ages the other ebXML spe­cific­a­tions, it can also be con­sidered sep­ar­ately, which is useful for the pur­poses of fo­cus­ing on mes­saging pat­terns.

                                                                                                                                                                                                                                                ebMS 并不像这里提到的其他一些标准那样“新兴”,因为它作为集成业务流程、增强 EDI 系统和扩展 SOAP 来为 Web 服务提供可靠性和安全性的手段,已经取得了巨大的成功。ebMS 由 OASIS 自 1999 年开始历时三年开发而成,事实证明,它对其他业务流程标准的开发也具有影响力。

                                                                                                                                                                                                                                                ebMS is not quite as "emer­ging" as some of the other stand­ards men­tioned here, as it has already achieved sig­ni­fic­ant suc­cess as a means of in­teg­rat­ing busi­ness pro­cesses, aug­ment­ing EDI sys­tems, and ex­tend­ing SOAP to provide re­li­ab­il­ity and se­cur­ity for Web ser­vices. De­ve­loped by OASIS over a three-year period be­gin­ning in 1999, ebMS is also prov­ing in­flu­en­tial in the de­vel­op­ment of other busi­ness pro­cess stand­ards.

                                                                                                                                                                                                                                                ebMS 的目标是简化 XML 框架内业务消息的交换,但该框架包括使用不一定是 XML 的消息有效负载,有效负载可以采用任何形式,包括传统的 EDI 格式和二进制格式。因此ebMS可以封装现有的消息系统并作为一种灵活的桥接技术,包含一组消息翻译器这对于扩展与外联网的集成以及业务合作伙伴之间的 B2B 通信特别有用。ebMS 用例的一个重要来源是跨不同企业的专有 MOM 系统的链接,并且供应商和开发人员之间已经进行了大量测试来验证这种互操作性。ebMS 的关键在于它支持传统的 EDI 系统,使企业能够利用对 EDI 的长期投资,同时通过结合 Web 和 XML 功能来弥补 EDI 的缺点。

                                                                                                                                                                                                                                                The goal of ebMS is to ease the ex­change of busi­ness mes­sages within an XML frame­work, but that frame­work in­cludes the use of mes­sage pay­loads that are not ne­ces­sar­ily XMLthe pay­load can take any form, in­clud­ing tra­di­tional EDI formats as well as binary formats. Thus ebMS can en­cap­su­late ex­ist­ing mes­saging sys­tems and serve as a flex­ible bridging tech­no­logy, con­tain­ing a set of Mes­sage Trans­lator im­ple­ment­a­tions, which is par­tic­u­larly useful in scal­ing in­teg­ra­tion to ex­tranets and to B2B com­mu­nic­a­tion between busi­ness part­ners. An im­port­ant source of use cases for ebMS is the link­age of pro­pri­et­ary MOM sys­tems across sep­ar­ate en­ter­prises, and con­sid­er­able test­ing between vendors and de­ve­lopers has oc­curred to verify this in­ter­op­er­ab­il­ity. Crit­ical to ebMS is that it sup­ports legacy EDI sys­tems, al­low­ing en­ter­prises to lever­age long­stand­ing in­vest­ments in EDI while com­pens­at­ing for EDI's short­com­ings by in­cor­por­at­ing fea­tures of the Web and XML.

                                                                                                                                                                                                                                                一方面,ebMS 是 EDI 的兼容迭代,另一方面,它也是基于标准 SOAP 服务的复杂改进。ebMS 系统中使用的 XML 传输由 SOAP 信封组成,其中包含 ebMS 特定的 SOAP 标头,用于记录唯一的消息标识符、时间戳、数字签名以及提供有关消息有效负载的元数据的清单。因此,ebMS 使用 SOAP 标头机制来实现消息路由模式和许多消息传递端点模式,例如幂等接收器和与保证顺序传递相关的其他模式。

                                                                                                                                                                                                                                                While on the one hand ebMS is a com­pat­ible it­er­a­tion of EDI, on the other hand it is also a soph­ist­ic­ated im­prove­ment of stand­ard SOAP-based ser­vices. The XML trans­mis­sions used in an ebMS system con­sist of SOAP en­vel­opes that in­clude ebMS-spe­cific SOAP Head­ers to record unique mes­sage iden­ti­fi­ers, timestamps, di­gital sig­na­tures, and a mani­fest which provides metadata about the mes­sage's pay­load. Thus, ebMS uses the SOAP Header mech­an­ism to im­ple­ment the mes­sage rout­ing pat­terns and many of the mes­saging en­d­points pat­terns, such as Idem­potent Re­ceiver and others re­lated to guar­an­teed se­quen­tial de­liv­ery.

                                                                                                                                                                                                                                                ebMS 通过使用带有附件的 SOAP 标准来传输其有效负载,将有效负载附加到 SOAP 信封,其方式与将附件添加到电子邮件消息的方式非常相似。同样,这些有效负载没有格式限制:消息的有效负载可以是 XML 数据、二进制数据、对外部数据的基于链接的引用等。此外,每个有效负载可以具有其自己的数字签名,并且附加的认证和授权可以由ebMS实现者提供。

                                                                                                                                                                                                                                                ebMS trans­ports its pay­loads by using the SOAP with At­tach­ments stand­ard to attach the pay­load to the SOAP en­vel­ope in much the same way that at­tach­ments are added to email mes­sages. These pay­loads, again, have no format­ting re­stric­tions placed upon them: A mes­sage's pay­load may be XML data, binary data, link-based ref­er­ences to ex­ternal data, and so on. Fur­ther, each pay­load may have its own di­gital sig­na­ture, and ad­di­tional au­then­tic­a­tion and au­thor­iz­a­tion may be provided by ebMS im­ple­menters.

                                                                                                                                                                                                                                                ebMS 消息由带有附件的SOAP 消息组成

                                                                                                                                                                                                                                                An ebMS Mes­sage Is Com­posed as SOAP Mes­sages with At­tach­ments

                                                                                                                                                                                                                                                图形/14inf02.gif

                                                                                                                                                                                                                                                默认情况下异步消息传递,但也可以通过 ebMS 进行同步传递。错误处理机制也相当复杂,并且根据实现可以提供 SOAP 错误信息以及特定于负载的错误消息。

                                                                                                                                                                                                                                                Asyn­chron­ous mes­sage de­liv­ery is the de­fault, but syn­chron­ous de­liv­ery is also pos­sible through ebMS. The error-hand­ling mech­an­ism is also quite soph­ist­ic­ated, and de­pend­ing on the im­ple­ment­a­tion can provide SOAP Fault in­form­a­tion along with pay­load-spe­cific error mes­sages.

                                                                                                                                                                                                                                                为了提供可靠性,ebMS 实现的关键元素(称为 ebXML 消息服务处理程序)将消息保留在对话的发送端。开发人员可以为单个消息或消息组提供语义声明,例如“一次且仅一次”和“存储并转发”。ebMS 还指定了管理顺序交付和管理的服务;最后一项服务是通过代表控制总线的消息状态服务来实现的。它提供了请求先前发送到 ebMS 系统的消息状态的能力,该系统在幕后通过消息历史记录和相关模式的实现进行操作。

                                                                                                                                                                                                                                                To provide re­li­ab­il­ity, the crit­ical ele­ment of an ebMS im­ple­ment­a­tion­called ebXML Mes­sage Ser­vice Hand­ler­sper­sist mes­sages at the send­ing end of a con­ver­sa­tion. De­ve­lopers can provide se­mantic de­clar­a­tions such as "once-and-only-once" and "store-and-for­ward" for in­di­vidual mes­sages or for groups of mes­sages. ebMS spe­cifies ser­vices to manage se­quen­tial de­liv­ery and man­age­ment as well; this last ser­vice is im­ple­men­ted through a Mes­sage Status Ser­vice that rep­res­ents a Con­trol Bus. It provides the abil­ity to re­quest the status of a mes­sage pre­vi­ously sent into the ebMS system, which un­der­neath the covers op­er­ates through an im­ple­ment­a­tion of Mes­sage His­tory and re­lated pat­terns.

                                                                                                                                                                                                                                                有关 ebXML 的更多信息,请访问http://www.ebxml.org/ebMS 规范可在http://www.ebxml.org/specs/ebMS2.pdf中找到。

                                                                                                                                                                                                                                                More in­form­a­tion on ebXML can be found at http://www.ebxml.org/. The ebMS spe­cific­a­tion can be found at http://www.ebxml.org/specs/ebMS2.pdf.

                                                                                                                                                                                                                                                Web 服务的业务流程执行语言 (BEPL4WS)

                                                                                                                                                                                                                                                Busi­ness Pro­cess Ex­e­cu­tion Lan­guage for Web Ser­vices (BEPL4WS)

                                                                                                                                                                                                                                                用于定义业务流程组件及其交互的流行新兴标准是 Web 服务业务流程执行语言 (BPEL4WS)。这个名字通常发音为“bee-pel”,甚至更糟糕,“bee-pel”的意思是“wuss”。该标准代表了来自 IBM 和 Microsoft 的两个竞争提案的合并。IBM 的 Web 服务流语言 (WSFL) 指定了一种创建和链接服务端点以编排 Web 服务工作流的方法,而 Microsoft 的 XLANG 提供了用于创建在 Microsoft BizTalk 服务器产品中实现的工作流组件的语法和开发模型。BPEL4WS 提供了两者的一部分:它包括用于从现有服务组合流程和描述流程接口的语法,以便在更大的工作流中将它们链接在一起。它是由 BEA Systems、IBM 和 Microsoft 之间的临时合作创建的,并已提交给 OASIS。

                                                                                                                                                                                                                                                A pop­u­lar emer­ging stand­ard for de­fin­ing busi­ness pro­cess com­pon­ents and their in­ter­ac­tions is Busi­ness Pro­cess Ex­e­cu­tion Lan­guage for Web Ser­vices (BPEL4WS). The name is often pro­nounced bee-pelor even worse, bee-pel for wuss. This stand­ard rep­res­ents the mer­ging of two com­pet­ing pro­pos­als from IBM and from Mi­crosoft. IBM's Web Ser­vices Flow Lan­guage (WSFL) spe­cified a means of cre­at­ing and link­ing ser­vice en­d­points to cho­reo­graph Web ser­vice work­flows, while Mi­crosoft's XLANG provided a syntax and de­vel­op­ment model for cre­at­ing work­flow com­pon­ents as real­ized in the Mi­crosoft BizTalk server product. BPEL4WS provides a bit of both: It in­cludes a syntax for com­pos­ing pro­cesses from ex­ist­ing ser­vices and for de­scrib­ing pro­cess in­ter­faces for the pur­pose of link­ing them to­gether in larger work­flows. It was cre­ated by an ad hoc col­lab­or­a­tion between BEA Sys­tems, IBM, and Mi­crosoft, and has been sub­mit­ted to OASIS.

                                                                                                                                                                                                                                                BPEL 的设计要求 Web 服务的链接(称为合作伙伴),根据称为活动将 XML 消息放入或取出消息存储(称为容器)。服务链接、消息容器和活动的逻辑集构成单个业务流程组件。BPEL 规范本质上定义了一个流程管理器,用于创建Routing Slip 、 Durable Subscriber 、 Datatype Channel,以及其他基于声明性 XML 语法的内容。开发人员在 XML 中声明行为(也许借助可视化工具),以便制作业务流程组件(例如第 630 页上的图中所示的组件),并且不需要以编程方式创建服务之间的消息传递。

                                                                                                                                                                                                                                                The design of BPEL calls for link­ages of Web ser­vices, named part­ners, to put XML mes­sages into and out of mes­sage stores, called con­tain­ers, ac­cord­ing to rules called activ­it­ies. A lo­gical set of ser­vice link­ages, mes­sage con­tain­ers, and activ­it­ies com­prise a single busi­ness pro­cess com­pon­ent. The BPEL spe­cific­a­tion es­sen­tially defines a Pro­cess Man­ager that cre­ates Rout­ing Slip, Dur­able Sub­scriber, Data­type Chan­nel, and others based on a de­clar­at­ive XML syntax. De­ve­lopers de­clare be­ha­vi­ors in XML (per­haps with the aid of visual tools) in order to craft busi­ness pro­cess com­pon­ents (such as the one shown in the figure on page 630), and do not need to pro­gram­mat­ic­ally create the mes­saging between ser­vices.

                                                                                                                                                                                                                                                WSDL 文档对于 BPEL 非常重要,因为 BPEL 组件将包含并管理基于服务的 WSDL 文档的多个 Web 服务。业务流程使用根据 WSDL 消息声明格式化的消息来实例化消息并将其路由到在这些服务的 WSDL 文档中声明的服务端点。BPEL 语法通过导入一组 Web 服务的 WSDL 文件并声明使用什么序列来接收服务消息、将消息发送到何处、何时以及如何回复、何时调用其他 Web 服务,从而链接一组 Web 服务的 portType和操作,以及等等。

                                                                                                                                                                                                                                                WSDL doc­u­ments are very im­port­ant to BPEL, as the BPEL com­pon­ent will con­sist of and manage mul­tiple Web ser­vices based on the ser­vices' WSDL doc­u­ments. The busi­ness pro­cess in­stan­ti­ates and routes mes­sages to the ser­vice en­d­points de­clared within these ser­vices' WSDL doc­u­ments using mes­sages that are format­ted ac­cord­ing to the WSDL mes­sage de­clar­a­tions. The BPEL syntax links the port­Type and op­er­a­tions of a set of Web ser­vices by im­port­ing their WSDL files and de­clar­ing what se­quences to use to re­ceive the ser­vice mes­sages, where to send them, when and how to reply, when to invoke other Web ser­vices, and so forth.

                                                                                                                                                                                                                                                为了建立服务之间的链接,应用程序开发人员导入组成组件的 Web 服务的 WSDL,并使用 BPEL serviceLinkType元素定义它们之间的合作伙伴关系。该元素指的是流程中涉及的 Web 服务的portType以及它们在流程中扮演的合作伙伴角色;然后,开发人员在声明消息应如何流入和流出这些链接和合作伙伴角色时就能够引用这些链接和合作伙伴角色。

                                                                                                                                                                                                                                                In order to es­tab­lish the link­ages between ser­vices, the ap­plic­a­tion de­ve­loper im­ports the WSDL of the Web ser­vices com­pris­ing the com­pon­ent and defines the part­ner re­la­tion­ships between them using the BPEL ser­viceLink­Type ele­ment. This ele­ment refers to the port­Types of the Web ser­vices in­volved in a flow and the part­ner roles that they play in re­la­tion to the pro­cess; the de­ve­loper is then able to refer to these link­ages and part­ner roles when de­clar­ing how mes­sages should flow into and out of them.

                                                                                                                                                                                                                                                声明服务之间的关系后,开发人员使用 BPEL 创建容器来保存服务用作输入和输出的消息。容器非常类似于WSDL 中定义的消息类型的数据类型通道。根据底层容器实现,也可以从共享数据库集成风格的角度考虑容器,但增加了封装和内置协作语义;容器是由单独的服务使用的共享数据存储库。除了通过 BPEL 元素将容器连接到服务之外,还可以通过 XPath 扩展直接访问容器的内容。

                                                                                                                                                                                                                                                After de­clar­ing the re­la­tion­ships between ser­vices, the de­ve­loper uses BPEL to create con­tain­ers to hold the mes­sages that the ser­vices use as input and output. Con­tain­ers are very much like a Data­type Chan­nel for the mes­sage types defined in WSDL. De­pend­ing on the un­der­ly­ing con­tainer im­ple­ment­a­tion, a con­tainer could also be con­sidered from the per­spect­ive of the Shared Data­base in­teg­ra­tion style, but with added en­cap­su­la­tion and built-in col­lab­or­a­tion se­mantics; a con­tainer is a shared data re­pos­it­ory used by sep­ar­ate ser­vices. In ad­di­tion to con­nect­ing con­tain­ers to ser­vices through the BPEL ele­ments, a con­tainer's con­tents can be ac­cessed dir­ectly via XPath ex­ten­sions.

                                                                                                                                                                                                                                                BPEL 组件本身将其自己的输入和输出点公开为单个接口,并且其这样做的形式与其内部 Web 服务使用的形式相同:通过 WSDL。然而,很明显,业务流程组件的 WSDL 与 Web 服务 WSDL 文档略有不同,因为业务流程 WSDL 的 portType定义进入和离开该单个流程的入口点,而不是实现的单独逻辑块。作为服务接口中的方法。

                                                                                                                                                                                                                                                A BPEL com­pon­ent itself ex­poses its own input and output points as a single in­ter­face, and it does so in the same form that its in­ternal Web ser­vices use: through WSDL. It should be evid­ent, how­ever, that the WSDL of a busi­ness pro­cess com­pon­ent is slightly dif­fer­ent from a Web ser­vice WSDL doc­u­ment in that the busi­ness pro­cess WSDL's port­Types define entry points into and out of that single pro­cess, and not sep­ar­ate pieces of logic im­ple­men­ted as meth­ods in a ser­vice in­ter­face.

                                                                                                                                                                                                                                                BPEL 组件的行为被声明为一组操作。在任何一个特定操作中,业务流程可以向服务发送消息(在这种情况下,Web 服务称为调用伙伴 、从服务接收消息(在这种情况下,服务称为客户端伙伴)、回复对于客户端合作伙伴发送的消息,根据某种逻辑规则确定是否应该发送或接收消息、等待预定的时间、报告错误、将消息从一个地方复制到另一个地方,或者什么都不做。

                                                                                                                                                                                                                                                The be­ha­vior of a BPEL com­pon­ent is de­clared as a set of ac­tions. In any one spe­cific action, a busi­ness pro­cesses can send mes­sages to a ser­vice (in this case, the Web ser­vice is called an in­voked part­ner), re­ceive mes­sages from a ser­vice (in this case, the ser­vice is called a client part­ner), reply to a mes­sage sent by a client part­ner, de­term­ine whether it should send or re­ceive mes­sages based on some lo­gical rule, wait for a sched­uled period of time, report an error, copy a mes­sage from one place to an­other, or do noth­ing at all.

                                                                                                                                                                                                                                                BPEL XML 语法反映了这些基本操作中的每一个。开发人员使用调用元素将消息发送到被调用的合作伙伴,并使用接收回复来接收和回复客户端合作伙伴。诸如分叉逻辑之类的元素进入并行和基于事件的执行通道。throw元素有助于错误报告,而waitemptyTerminate元素则暂停或停止执行。构造活动的元素包括whilesequencepick,并且 BPEL 中的条件是使用 XPath 语句声明的。

                                                                                                                                                                                                                                                The BPEL XML gram­mar re­flects each of these basic ac­tions. A de­ve­loper uses the invoke ele­ment to send mes­sages to an in­voked part­ner and uses re­ceive and reply to re­ceive and reply to a client part­ner. Ele­ments such as flow and pick fork logic into par­al­lel and event-based chan­nels of ex­e­cu­tion. The throw ele­ment fa­cil­it­ates error re­port­ing, while the wait, empty, and ter­min­ate ele­ments pause or halt ex­e­cu­tion. Ele­ments to struc­ture activ­it­ies in­clude while, se­quence, and pick, and con­di­tion­als in BPEL are de­clared using XPath state­ments.

                                                                                                                                                                                                                                                一旦开发人员创建了定义将组成业务流程的服务的 XML 源文件,声明了服务将使用的消息容器,并声明了消息交换中涉及的操作序列,他或她就准备好部署该业务流程了。业务流程组件。这就是 BPEL 实现的运行时方面发挥作用的地方。

                                                                                                                                                                                                                                                Once a de­ve­loper has cre­ated the XML source file that defines the ser­vices that will com­pose a busi­ness pro­cess, de­clares the mes­sage con­tain­ers that the ser­vices will use, and de­clares the se­quence of op­er­a­tions in­volved in the mes­sage ex­change, he or she is ready to deploy the busi­ness pro­cess com­pon­ent. This is where the runtime as­pects of BPEL im­ple­ment­a­tions come into play.

                                                                                                                                                                                                                                                除了充当服务关系和消息流的声明之外,BPEL 描述也是可以输入 BPEL4WS 引擎的可执行文件。当发生这种情况时,引擎解释该文件并为应用程序开发人员设置一系列消息传递结构,以连接作为业务流程一部分的 Web 服务。作为流程管理器实现,BPEL4WS 运行时是管理和关联服务之间的消息流的管理实体。BPEL4WS 引擎接受所有必要的文档并动态生成和管理消息传递基础设施。

                                                                                                                                                                                                                                                In ad­di­tion to acting as de­clar­a­tions of ser­vice re­la­tion­ships and mes­sage flow, BPEL de­scrip­tions are also ex­ecut­able files that can be fed into a BPEL4WS engine. When this hap­pens, the engine in­ter­prets the file and sets up a series of mes­saging con­structs for the ap­plic­a­tion de­ve­loper that con­nects the Web ser­vices that are part of the busi­ness pro­cess. As a Pro­cess Man­ager im­ple­ment­a­tion, the BPEL4WS runtime is the gov­ern­ing entity that man­ages and cor­rel­ates the flow of mes­sages between ser­vices. The BPEL4WS engine ac­cepts all of the ne­ces­sary doc­u­ments and dy­nam­ic­ally gen­er­ates and man­ages the mes­saging in­fra­struc­ture.

                                                                                                                                                                                                                                                BPEL 规范可以在http://www-106.ibm.com/developerworks/webservices/library/ws-bpel/找到。

                                                                                                                                                                                                                                                The BPEL spe­cific­a­tion can be found at http://www-106.ibm.com/de­ve­loper­works/web­ser­vices/lib­rary/ws-bpel/.

                                                                                                                                                                                                                                                Web 服务编排接口 (WSCI)

                                                                                                                                                                                                                                                Web Ser­vice Cho­reo­graphy In­ter­face (WSCI)

                                                                                                                                                                                                                                                Web 服务编排接口(WSCI;通常读作“威士忌”)解决了 BPEL 解决的相同问题领域。这两个规范最初是由竞争供应商联盟支持的(BEA 除外,该公司似乎通过为这两个规范做出贡献来对冲赌注)。WSCI 得到了 Sun、Intalio、SAP 和 BEA 的支持,并已提交给 W3C。然而,WSCI 的一些最初支持者现在正在向 BPEL 提供支持。

                                                                                                                                                                                                                                                The Web Ser­vice Cho­reo­graphy In­ter­face (WSCI; often pro­nounced whis­key) ad­dresses the same prob­lem domain tackled by BPEL. The two were com­pet­ing spe­cific­a­tions ini­tially backed by com­pet­ing vendor al­li­ances (with the ex­cep­tion of BEA, which ap­pears to be hedging bets by con­trib­ut­ing to both spe­cific­a­tions). WSCI was backed by Sun, In­t­alio, SAP, and BEA, and has been sub­mit­ted to the W3C. How­ever, sev­eral ori­ginal sup­port­ers of WSCI are now lend­ing sup­port to BPEL.

                                                                                                                                                                                                                                                WSCI 受到业务流程建模语言 (BPML) 的影响,虽然与集成模式没有直接关系,但考虑到 WSCI 的历史背景,确实值得一提。BPML 用流程建模中使用的基于 XML 的元语言表示业务流程。BPML 的工作流程方面很大程度上是 WSCI 的一部分,但 BPML 还包括配套的图形符号和查询语言。

                                                                                                                                                                                                                                                WSCI is in­flu­enced by Busi­ness Pro­cess Mod­el­ing Lan­guage (BPML), which while not dir­ectly rel­ev­ant to in­teg­ra­tion pat­terns, does bear men­tion­ing for the sake of WSCI's his­tor­ical con­text. BPML rep­res­ents busi­ness pro­cesses in an XML-based metalan­guage used in pro­cess mod­el­ing. BPML's work­flow as­pects are largely part of WSCI, but BPML also in­cludes a com­pan­ion graph­ical nota­tion and query lan­guage.

                                                                                                                                                                                                                                                与 BPEL 一样,WSCI 认识到企业集成涉及组合服务之间的长时间对话,而不是基本 Web 服务协议所假定的单一操作调用。WSCI 提供了一种将来自多个操作的服务消息链接到这些复合流程中的方法,确保消息按照正确的顺序发送或接收,根据声明性业务规则发送和接收,在需要时以事务方式发送,并且可描绘和描述可作为单个全局流程进行管理。利用 Web 服务相对于传统专有 MOM 解决方案的优势,WSCI 还适应服务的动态发现、异构协议和工作流的分散协调。

                                                                                                                                                                                                                                                Like BPEL, WSCI re­cog­nizes that en­ter­prise in­teg­ra­tion in­volves lengthy con­ver­sa­tions among com­pos­ite ser­vices rather than the single op­er­a­tional in­voc­a­tions as­sumed by basic Web ser­vices pro­to­cols. WSCI provides a way to link ser­vice mes­sages from mul­tiple op­er­a­tions into these com­pos­ite pro­cesses, en­sur­ing along the way that mes­sages are sent or re­ceived in the proper se­quences, sent and re­ceived ac­cord­ing to de­clar­at­ive busi­ness rules, sent in trans­ac­tional fash­ion when needed, and por­tray­able and man­age­able as a single global pro­cess. Lever­aging Web ser­vice ad­vant­ages over tra­di­tional pro­pri­et­ary MOM solu­tions, WSCI also ac­com­mod­ates the dy­namic dis­cov­ery of ser­vices, het­ero­gen­eous pro­to­cols, and de­cent­ral­ized co­ordin­a­tion of work­flow.

                                                                                                                                                                                                                                                与 BPEL 一样,WSCI 严重依赖 WSDL 的服务端点、portTypes 、操作和消息类型的通告。WSCI 操作直接映射到编排中服务的 WSDL 中公开的操作。此外,WSCI 语法直接嵌入到 WSDL 文件中;这些声明要么位于 WSDL 文档中(该文档导入编排服务的 WSDL 文档),要么位于正在编排操作的单个服务的 WSDL 文件中。WSCI 元素包含在 WSDL定义元素中。

                                                                                                                                                                                                                                                Like BPEL, WSCI relies heav­ily upon WSDL's ad­vert­ise­ment of ser­vice en­d­points, port­Types, op­er­a­tions, and mes­sage types. WSCI ac­tions dir­ectly map to op­er­a­tions ex­posed in the WSDL of the ser­vices within a cho­reo­graphy. Moreover, the WSCI syntax is em­bed­ded dir­ectly within a WSDL file; the de­clar­a­tions sit either in a WSDL doc­u­ment, which im­ports the WSDL doc­u­ments of the cho­reo­graphed ser­vices, or within the WSDL file of a single ser­vice whose op­er­a­tions are being cho­reo­graphed. WSCI ele­ments are con­tained within the WSDL defin­i­tions ele­ment.

                                                                                                                                                                                                                                                WSCI 的基本构造是 Web 服务消息传递发生的操作。操作在流程元素内分组,以便声明它们按顺序、并行、循环或有条件地发生。一组进程通过接口声明进行分组;该接口元素是流程组件的接口,它直接嵌入到 Web 服务的 WSDL 定义中。许多活动元素与 BPEL 的活动元素类似,并且像它们的 BPEL 对应项一样,它们包括对 XPath 表达式的支持。

                                                                                                                                                                                                                                                The fun­da­mental con­struct of WSCI is the action in which Web ser­vice mes­saging occurs. Ac­tions are grouped within pro­cess ele­ments so that they are de­clared to occur se­quen­tially, in par­al­lel, in loop, or con­di­tion­ally. A set of pro­cesses is grouped with an in­ter­face de­clar­a­tion; this in­ter­face ele­ment is the in­ter­face to the pro­cess com­pon­ent, and it is dir­ectly em­bed­ded within the WSDL defin­i­tions of a Web ser­vice. Many of the activ­ity ele­ments are sim­ilar to those of BPEL, and like their BPEL coun­ter­parts, they in­clude sup­port for XPath ex­pres­sions.

                                                                                                                                                                                                                                                WSCI 规范可在http://wwws.sun.com/software/xml/developers/wsci/wsci-spec-10.pdf中找到。

                                                                                                                                                                                                                                                The WSCI spe­cific­a­tion can be found at http://wwws.sun.com/soft­ware/xml/de­ve­lopers/wsci/wsci-spec-10.pdf.

                                                                                                                                                                                                                                                Java 业务流程组件标准

                                                                                                                                                                                                                                                Java Busi­ness Pro­cess Com­pon­ent Stand­ards

                                                                                                                                                                                                                                                JCP 创建受非 Java 标准影响的 Java 语言 API 和绑定。特别是,对象管理组织 (OMG) 和 W3C 制定的标准已受到 JCP 的影响。最近,JCP 已将这种方法转向由 WS-I 等团体开发的 Web 服务标准,并且目前正在开发两个新的、大肆宣传的 JSR,以解决业务流程组件的 Java 绑定问题: Java 流程定义 (JSR-207) ),由 BEA Systems 提交,以及 Java Business Integration (JSR-208),由 Sun Microsystems 提交。虽然乍一看这两个提议似乎有重叠,但实际上它们是相当互补的。JSR-207 为开发人员指定了一种使用附加到 Java 代码的元数据快速轻松地制作消息传递或处理组件的方法;JSR-208 指定这些组件如何相互交互、与容器交互以及与 J2EE 和 Web 服务世界的其余部分交互。因此,JSR-207 或多或少是一个微观视图,而 JSR-208 是如何在 Java 中标准化流程组件之间的消息传递的宏观视图。

                                                                                                                                                                                                                                                The JCP cre­ates Java lan­guage APIs and bind­ings in­flu­enced by non-Java stand­ards. In par­tic­u­lar, stand­ards de­ve­loped by the Object Man­age­ment Group (OMG) and the W3C have been shad­owed by the JCP. Re­cently, the JCP has turned this ap­proach toward Web ser­vices stand­ards de­ve­loped by groups such as the WS-I, and two new, much-bal­ly­hooed JSRs are now un­der­way to ad­dress Java bind­ings for busi­ness pro­cess com­pon­ents: Pro­cess Defin­i­tion for Java (JSR-207), sub­mit­ted by BEA Sys­tems, and Java Busi­ness In­teg­ra­tion (JSR-208), sub­mit­ted by Sun Mi­crosys­tems. While at first glance these two pro­pos­als may seem to over­lap, they are ac­tu­ally fairly com­ple­ment­ary. JSR-207 spe­cifies a way for de­ve­lopers to craft mes­saging or pro­cess com­pon­ents quickly and easily using metadata at­tached to Java code; JSR-208 spe­cifies how those com­pon­ents will in­ter­act with each other, with con­tain­ers, and with the rest of the J2EE and Web ser­vices world. So, JSR-207 is more or less a micro-view, and JSR-208 is a macro-view of how the mes­saging between pro­cess com­pon­ents is to be stand­ard­ized in Java.

                                                                                                                                                                                                                                                Java 流程定义 (JSR-207)

                                                                                                                                                                                                                                                Process Definition for Java (JSR-207) 由 BEA Systems 提交,旨在定义用于在 Java/J2EE 环境中创建业务流程的元数据、接口和运行时模型。这个非常重要的 JSR 旨在指定使用 Java 语言和类似 Javadoc 的元数据注释来构建业务流程组件的标准方法。作为 J2EE 的补充,该机制还可用于构建业务流程计划的 Java 实现,例如 BPEL4WS、WSCI 以及 W3C Choreography 工作组生成的计划。

                                                                                                                                                                                                                                                Pro­cess Defin­i­tion for Java (JSR-207), sub­mit­ted by BEA Sys­tems, aims to define metadata, in­ter­faces, and a runtime model for cre­at­ing busi­ness pro­cesses in the Java/J2EE en­vir­on­ment. This very im­port­ant JSR in­tends to spe­cify the stand­ard means of craft­ing busi­ness pro­cess com­pon­ents using the Java lan­guage and Javadoc-like metadata an­nota­tions. Pro­posed as an ad­di­tion to J2EE, the mech­an­ism could also be used to build Java im­ple­ment­a­tions of busi­ness pro­cess ini­ti­at­ives such as BPEL4WS, WSCI, and those pro­duced by the W3C Cho­reo­graphy Work­ing Group.

                                                                                                                                                                                                                                                该技术建立在 Java 语言元数据技术 (JSR-175) 之上,以便提供用于描述业务流程的简单语法。元数据可以直接应用于Java源代码,以动态生成和绑定流程行为,包括对异步消息传递、并行执行、消息关联、消息路由、错误处理和其他常见流程活动的支持。因此,元数据语义需要足够丰富,以支持组件容器所需的参数,以便在组件部署时动态设置消息传递基础结构并处理第 5 章“消息构造”中介绍中描述的问题。

                                                                                                                                                                                                                                                This tech­no­logy builds upon Java Lan­guage Metadata tech­no­logy (JSR-175) in order to supply a simple syntax for de­scrib­ing busi­ness pro­cesses. Metadata can be ap­plied dir­ectly to Java source code to dy­nam­ic­ally gen­er­ate and bind pro­cess be­ha­vi­ors, in­clud­ing sup­port for asyn­chron­ous mes­saging, par­al­lel ex­e­cu­tion, mes­sage cor­rel­a­tion, mes­sage rout­ing, error hand­ling, and other common flow activ­it­ies. The metadata se­mantics there­fore need to be rich enough to sup­port the para­met­ers needed for a com­pon­ent's con­tainer to dy­nam­ic­ally set up the mes­saging in­fra­struc­ture and handle issues de­scribed in the in­tro­duc­tion in Chapter 5, "Mes­sage Con­struc­tion," upon the com­pon­ent's de­ploy­ment.

                                                                                                                                                                                                                                                值得注意的是,开发人员在 J2EE 中构建业务流程组件不需要此 JSR。如今构建这样的流程是可能的,但这是一项费力的工作,需要开发人员在非常低的级别应用消息传递模式,并导致工作流程的维护成本高昂。该规范旨在简化流程组件的创建,以便开发人员可以在更高的水平上应用他们的技能,更快地创建更强大的应用程序,并且随着时间的推移,开发和管理的成本更低。

                                                                                                                                                                                                                                                It is worth noting that this JSR is not needed to enable de­ve­lopers to build busi­ness pro­cess com­pon­ents in J2EE. It is pos­sible to build such pro­cesses today­but it is la­bor­i­ous work that re­quires de­ve­lopers to apply mes­saging pat­terns at a very low level and res­ults in work­flows that are costly to main­tain. This spe­cific­a­tion in­tends to sim­plify the cre­ation of pro­cess com­pon­ents so that de­ve­lopers can apply their skills at a higher level, cre­at­ing more power­ful ap­plic­a­tions more rap­idly that are less ex­pens­ive to evolve and ad­min­is­ter over time.

                                                                                                                                                                                                                                                有关 Java 流程定义的更多详细信息,请访问http://www.jcp.org/en/jsr/detail?id=207

                                                                                                                                                                                                                                                More de­tails on Pro­cess defin­i­tion for Java can be found at http://www.jcp.org/en/jsr/detail?id=207.

                                                                                                                                                                                                                                                Java 业务集成 (JSR-208)

                                                                                                                                                                                                                                                Sun 在 JSR-208 中提出了 Java 业务集成 (JBI),旨在定义服务提供者接口 (SPI),以便为 WSCI、BPEL4WS 等规范以及 W3C Choreography 工作组制定的工作创建业务集成环境。JBI 没有提出新的 Java API 或注释,但包含新的部署和打包机制。JBI 没有为开发人员添加 API,而是专注于集成基础设施,其 SPI 主要对创建消息传递和流程组件模型以在 Java 设置中执行集成工作的产品供应商可见。JBI 专家组的目标是遵循 W3C 编排工作组的领导,并确保该组的工作能够无缝地融入 J2EE 平台。

                                                                                                                                                                                                                                                Sun pro­posed Java Busi­ness In­teg­ra­tion (JBI) in JSR-208 with the in­ten­tion of de­fin­ing ser­vice pro­vider in­ter­faces (SPIs) for cre­at­ing a busi­ness in­teg­ra­tion en­vir­on­ment for spe­cific­a­tions such as WSCI, BPEL4WS, and the work pro­duced by the W3C Cho­reo­graphy Work­ing Group. JBI pro­poses no new Java APIs or an­nota­tions, but does in­clude a new de­ploy­ment and pack­aging mech­an­ism. In­stead of adding APIs for de­ve­lopers, JBI fo­cuses on in­teg­ra­tion in­fra­struc­ture, and its SPIs will be vis­ible primar­ily to product vendors who create mes­saging and pro­cess com­pon­ent models for per­form­ing in­teg­ra­tion work in a Java set­ting. The JBI expert group aims to follow the lead of the W3C Cho­reo­graphy Work­ing Group and ensure that the work of that group will fit seam­lessly into the J2EE plat­form.

                                                                                                                                                                                                                                                JBI 有一个相当崇高的目标:映射各种系统和协议标准,即用于描述进程之间关系的多种语法,包括专有的和特定于供应商的语法以及成为彼此标准和 J2EE 标准的语法。它为消息编排提供 Java 绑定,而不管底层消息和流程细节如何。它还包括一个新的打包机制,该机制扩展了 J2EE 打包(例如 WAR、JAR、RAR 和 EAR)以支持将 JBI 组件部署到 J2EE 环境中。

                                                                                                                                                                                                                                                JBI has a fairly lofty goal: Map vari­ous sys­tems and pro­tocol stand­ard­sthat is, mul­tiple syn­taxes used to de­scribe re­la­tion­ships between pro­cesses, in­clud­ing syn­taxes that are pro­pri­et­ary and vendor-spe­cific as well as those that become stand­ardto one an­other and to J2EE. It provides a Java bind­ing for mes­sage cho­reo­graphy re­gard­less of the un­der­ly­ing mes­sage and pro­cess spe­cif­ics. It also in­cludes a new pack­aging mech­an­ism that ex­tends J2EE pack­aging (such as WAR, JAR, RAR, and EAR) to sup­port the de­ploy­ment of a JBI com­pon­ent into a J2EE en­vir­on­ment.

                                                                                                                                                                                                                                                JBI 认为支持流程组件所需的三个主要角色:绑定、机器和环境。绑定与通信格式有关,包括消息格式和网络传输及其映射,并围绕工作流格式(例如 BPEL)形成一个保护伞;机器是托管和管理业务流程的服务和流程容器;该环境是一个总体流程管理系统,它将异构机器和绑定相互链接。JBI 关注环境作为集成系统的核心,并指定机器和绑定如何与其交互。JBI 打包和部署机制旨在提供一种以标准方式将进程与环境挂钩的方法,

                                                                                                                                                                                                                                                JBI sees three prin­cipal roles re­quired for sup­port­ing pro­cess com­pon­ents: bind­ings, ma­chines, and the en­vir­on­ment. Bind­ings are about com­mu­nic­a­tion formats and in­clude mes­sage formats and net­work trans­ports along with their map­pings as well as form an um­brella around work­flow formats such as BPEL; ma­chines are the ser­vice and pro­cess con­tain­ers that host and manage busi­ness pro­cesses; the en­vir­on­ment is the over­arch­ing pro­cess man­age­ment system that links het­ero­gen­eous ma­chines and bind­ings to one an­other. JBI fo­cuses on the en­vir­on­ment as the core of the in­teg­ra­tion system and spe­cifies how ma­chines and bind­ings in­ter­act with it. The JBI pack­aging and de­ploy­ment mech­an­ism is in­ten­ded to provide a means of hook­ing pro­cesses up to the en­vir­on­ment in a stand­ard way, but the actual cre­ation of those com­pon­ents is out­side the scope of the spe­cific­a­tion.

                                                                                                                                                                                                                                                JBI 将上述 Java 流程定义 (JSR-207) 视为组合流程组件的一种方式,并且托管这些组件的运行时适合 JBI 机器的类别。该机器应该实现Message Translator 、 Service Activator 、 Envelope Wrapper 以及通过 JBI 公开其格式和协议行为所需的其他模式,以确保它可以与集成环境正确集成。

                                                                                                                                                                                                                                                JBI views the Pro­cess Defin­i­tion for Java (JSR-207) de­scribed above as a way of com­pos­ing pro­cess com­pon­ents, and the runtime that will host those com­pon­ents fits into the cat­egory of a JBI ma­chine. That ma­chine should im­ple­ment the Mes­sage Trans­lator, Ser­vice Ac­tiv­ator, En­vel­ope Wrap­per and other pat­terns ne­ces­sary to expose their formats and pro­tocol be­ha­vi­ors through JBI to ensure that it can in­teg­rate cor­rectly with the in­teg­ra­tion en­vir­on­ment.

                                                                                                                                                                                                                                                有关 JBI 的更多详细信息,请访问http://www.jcp.org/en/jsr/detail?id=208

                                                                                                                                                                                                                                                More de­tails on JBI can be found at http://www.jcp.org/en/jsr/detail?id=208.

                                                                                                                                                                                                                                                WS-*

                                                                                                                                                                                                                                                WS-*

                                                                                                                                                                                                                                                与刚刚描述的流程集成规范相比,许多 Web 服务规范的雄心勃勃,但正在出现许多 Web 服务规范来扩展基于 SOAP 和 WSDL 的 Web 服务,以包括可靠性、安全性、状态性和服务质量。这些规范通常可通过WS-前缀进行识别,它们建立在 W3C 技术之上,并且每个规范都解决一个相当具体的问题。

                                                                                                                                                                                                                                                Less am­bi­tious than the pro­cess in­teg­ra­tion spe­cific­a­tions just de­scribed, a number of Web ser­vices spe­cific­a­tions are emer­ging to extend SOAP- and WSDL-based Web ser­vices to in­clude re­li­ab­il­ity, se­cur­ity, state­ful­ness, and qual­ity of ser­vice. Typ­ic­ally iden­ti­fi­able by the WS- prefix, these spe­cific­a­tions build on the W3C tech­no­lo­gies, and each ad­dresses a fairly spe­cific prob­lem.

                                                                                                                                                                                                                                                不幸的是,Web 服务标准格局已经变得混乱不堪,充斥着由相互竞争的供应商联盟支持的相互竞争的版本,并且也许正是这种竞争的结果,许多这些标准如今很少被广泛实施。标准的调整似乎即将来临。尽管如此,这些规范还是值得注意的,因为它们的习惯用法和策略可能对将 SOAP 和 WSDL 等 Web 服务技术应用于基于消息传递的企业集成的应用程序开发人员有用。这些标准也肯定会在提出这些标准的供应商的产品中找到一席之地,包括 IBM、BEA、Microsoft 和 Oracle 的产品。

                                                                                                                                                                                                                                                Un­for­tu­nately, the Web ser­vices stand­ards land­scape has become muddled with com­pet­ing ver­sions backed by com­pet­ing vendor al­li­ances, and per­haps as a result of this com­pet­i­tion, many of these stand­ards are rarely seen im­ple­men­ted in the wild today. A stand­ards shake-out seems to be loom­ing. Nev­er­the­less, these spe­cific­a­tions are worth noting, as their idioms and tac­tics might prove useful to ap­plic­a­tion de­ve­lopers who are ap­ply­ing Web ser­vices tech­no­lo­gies like SOAP and WSDL to mes­saging-based en­ter­prise in­teg­ra­tion. These stand­ards are also cer­tain to find a home in the products of the vendors who pro­pose them, in­clud­ing those of IBM, BEA, Mi­crosoft, and Oracle.

                                                                                                                                                                                                                                                此类别中一些更值得注意的规范包括处理可事务性、可靠性、路由、会话状态和安全性的规范。这些内容将在以下几页中进行描述。

                                                                                                                                                                                                                                                A few of the more not­able spe­cific­a­tions in this cat­egory in­clude those deal­ing with trans­act­ab­il­ity, re­li­ab­il­ity, rout­ing, con­ver­sa­tional state, and se­cur­ity. These are de­scribed in the fol­low­ing pages.

                                                                                                                                                                                                                                                WS-协调和 WS-事务

                                                                                                                                                                                                                                                由于无状态且不可靠,最流行的 Web 服务协议和传输无法提供事务处理所需的服务质量。这个缺点发展成为一个极其重要的问题。实际上,这意味着如果开发人员希望使用基于 Web 服务的集成机制,则该开发人员必须在这些集成机制中发展他或她自己的事务方案。通过异步消息传递发生的服务调用的集合必须能够以原子方式进行批处理,以便消息作为一个可以同时失败或回滚的单元,或者作为一个失败可以触发某种形式的补偿的单元。这在专有 MOM 系统中很常见。

                                                                                                                                                                                                                                                Being state­less and un­re­li­able, the most pop­u­lar Web ser­vices pro­to­cols and trans­ports do not provide the qual­ity of ser­vice re­quired by trans­ac­tional pro­cesses. This short­com­ing blos­soms into a crit­ic­ally im­port­ant prob­lem. In prac­tice, it means that if a de­ve­loper wishes to use a Web ser­vices­based in­teg­ra­tion mech­an­ism, that de­ve­loper must grow his or her own trans­ac­tion scheme within those in­teg­ra­tion mech­an­isms. A col­lec­tion of ser­vice in­voc­a­tions that occur through asyn­chron­ous mes­saging must be cap­able of being batched atom­ic­ally so that the mes­sages func­tion as a unit that can fail or roll back all at once or as a unit whose fail­ure can trig­ger some form of com­pens­a­tion. This is common in pro­pri­et­ary MOM sys­tems. The Web Ser­vices Co­ordin­a­tion and Web Ser­vices Trans­ac­tion spe­cific­a­tions tackle this prob­lem for Web ser­vices.

                                                                                                                                                                                                                                                WS-Coordination 由 BEA、Microsoft 和 IBM 起草,指定了一种由参与流的所有服务创建和传播上下文信息的方法,甚至可以异步地并在锯齿状时间间隔内传播。该规范描述了一个可扩展框架,用于创建协调应用程序和服务的操作的协议。这些协调协议通过创建和注册基于 XML 的上下文来发挥作用,这些上下文通过 SOAP 消息传播并由位于交互中所有端点的协调器使用。

                                                                                                                                                                                                                                                WS-Co­ordin­a­tion, draf­ted by BEA, Mi­crosoft, and IBM, spe­cifies a way to create and propag­ate con­tex­tual in­form­a­tion by all of the ser­vices par­ti­cip­at­ing in a flow, even asyn­chron­ously and over jagged time in­ter­vals. The spe­cific­a­tion de­scribes an ex­tens­ible frame­work for cre­at­ing pro­to­cols that co­ordin­ate the ac­tions of ap­plic­a­tions and ser­vices. These co­ordin­a­tion pro­to­cols func­tion by cre­at­ing and re­gis­ter­ing XML-based con­texts that are propag­ated with SOAP mes­sages and used by co­ordin­at­ors loc­ated at all en­d­points in an in­ter­ac­tion.

                                                                                                                                                                                                                                                此类上下文可用于支持许多应用程序行为,例如需要就分布式事务的结果达成一致的行为。因此,WS-Transaction 规范使用 WS-Coordination 来实现跨服务调用的分布式事务性。

                                                                                                                                                                                                                                                Such con­texts can be used to sup­port a number of ap­plic­a­tion be­ha­vi­ors, such as those that need to reach con­sist­ent agree­ment on the out­come of dis­trib­uted trans­ac­tions. Ac­cord­ingly, the WS-Trans­ac­tion spe­cific­a­tion uses WS-Co­ordin­a­tion to im­ple­ment dis­trib­uted trans­act­ab­il­ity across ser­vice in­voc­a­tions.

                                                                                                                                                                                                                                                WS-Transaction 定义了一种监视和衡量流程中每个操作成功或失败的方法。实际上,这意味着当 SOAP 消息到达端点时,必须过滤并从消息中提取包含协调上下文的 SOAP 标头(实现可以使用内容过滤器和拆分器模式来执行此任务),然后将其发送到事务协调器以便解释。

                                                                                                                                                                                                                                                WS-Trans­ac­tion defines a way to mon­itor and meas­ure the suc­cess or fail­ure of each action in a flow. Prac­tic­ally, this means that when a SOAP mes­sage ar­rives at an en­d­point, the SOAP Header con­tain­ing the co­ordin­a­tion con­text must be filtered and pulled from the mes­sage (im­ple­ment­a­tions may employ the Con­tent Filter and Split­ter pat­terns for this task) and then sent to the trans­ac­tion co­ordin­ator for in­ter­pret­a­tion.

                                                                                                                                                                                                                                                下面的 SOAP 信封摘录提供了一个协调上下文的简单示例,该协调上下文用于使 SOAP 操作可通过 WS-Transaction 进行事务处理。协调器使用 SOAP 标头中的上下文信息来注册应用程序以接收事务事件,例如登记、两阶段提交过程的准备阶段、回滚和提交。

                                                                                                                                                                                                                                                The SOAP en­vel­ope ex­cerpt below provides a simple ex­ample of a co­ordin­a­tion con­text used for making SOAP op­er­a­tions trans­act­able through WS-Trans­ac­tion. This con­text in­form­a­tion in the SOAP header is used by co­ordin­at­ors to re­gister ap­plic­a­tions to re­ceive trans­ac­tion events such as en­list­ments, the pre­pare stage of a two-phase commit pro­cess, roll­backs, and com­mits.

                                                                                                                                                                                                                                                <SOAP-ENV:信封 xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope">
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                  <SOAP-ENV:标头>
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                    <wscoor:CoordinationContext
                                                                                                                                                                                                                                                            xmlns:wscoor="http://schemas.xmlsoap.org/ws/2002/08/wscoor"
                                                                                                                                                                                                                                                            xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"
                                                                                                                                                                                                                                                            xmlns:myTransactableApp="http://foo.com/baz">
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                      <wsu:标识符>http://foo.com/baz/bar</wsu:标识符>
                                                                                                                                                                                                                                                      <wsu:过期>2004-12-31T18:00:00-08:00</wsu:过期>
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                      <wscoor:协调类型>
                                                                                                                                                                                                                                                        http://schemas.xmlsoap.org/ws/2002/08/wstx
                                                                                                                                                                                                                                                      </wscoor:协调类型>
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                      <wscoor:注册服务>
                                                                                                                                                                                                                                                        <wsu:地址>
                                                                                                                                                                                                                                                          http://foo.com/coordinationservice/registration
                                                                                                                                                                                                                                                        </wsu:地址>
                                                                                                                                                                                                                                                      </wscoor:注册服务>
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                      <myTransactableApp:隔离级别>
                                                                                                                                                                                                                                                        可重复读取
                                                                                                                                                                                                                                                      </myTransactableApp:隔离级别>
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                    </wscoor:CoordinationContext>
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                  </SOAP-ENV:标头>
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                  <!-- 肥皂体(剪断)-->
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                </SOAP-ENV:信封>
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                <SOAP-ENV:En­vel­ope xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-en­vel­ope">
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                  <SOAP-ENV:Header>
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                    <wscoor:Co­ordin­a­tion­Con­text
                                                                                                                                                                                                                                                            xmlns:wscoor="http://schemas.xmlsoap.org/ws/2002/08/wscoor"
                                                                                                                                                                                                                                                            xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/util­ity"
                                                                                                                                                                                                                                                            xmlns:myTrans­act­able­App="http://foo.com/baz">
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                      <wsu:Iden­ti­fier>http://foo.com/baz/bar</wsu:Iden­ti­fier>
                                                                                                                                                                                                                                                      <wsu:Ex­pires>2004-12-31T18:00:00-08:00</wsu:Ex­pires>
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                      <wscoor:Co­ordin­a­tion­Type>
                                                                                                                                                                                                                                                        http://schemas.xmlsoap.org/ws/2002/08/wstx
                                                                                                                                                                                                                                                      </wscoor:Co­ordin­a­tion­Type>
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                      <wscoor:Re­gis­tra­tion­Ser­vice>
                                                                                                                                                                                                                                                        <wsu:Ad­dress>
                                                                                                                                                                                                                                                          http://foo.com/co­ordin­a­tion­ser­vice/re­gis­tra­tion
                                                                                                                                                                                                                                                        </wsu:Ad­dress>
                                                                                                                                                                                                                                                      </wscoor:Re­gis­tra­tion­Ser­vice>
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                      <myTrans­act­able­App:Isol­a­tion­Level>
                                                                                                                                                                                                                                                        Re­peat­a­bleRead
                                                                                                                                                                                                                                                      </myTrans­act­able­App:Isol­a­tion­Level>
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                    </wscoor:Co­ordin­a­tion­Con­text>
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                  </SOAP-ENV:Header>
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                  <!-- SOAP BODY (snipped) -->
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                </SOAP-ENV:En­vel­ope>
                                                                                                                                                                                                                                                

                                                                                                                                                                                                                                                WS-Transaction 规范定义了两种协调类型供开发人员在其应用程序中使用:原子事务 (AT) 和业务活动 (BA)。

                                                                                                                                                                                                                                                The WS-Trans­ac­tion spe­cific­a­tion defines two co­ordin­a­tion types for de­ve­lopers to use in their ap­plic­a­tions: atomic trans­ac­tion (AT) and busi­ness activ­ity (BA).

                                                                                                                                                                                                                                                原子事务很好地映射到传统的分布式事务技术,例如 XA。它们对于相对短暂的操作很有用,在这些操作中,诸如线程和部分数据源之类的资源锁定是可以接受的,并且绝对回滚是有意义的。WS-Transaction 提供了一种将专有 XA 实现(包括对两阶段提交的支持)链接到 Web 服务的方法。

                                                                                                                                                                                                                                                Atomic trans­ac­tions map fairly well to tra­di­tional dis­trib­uted trans­ac­tion tech­no­logy, such as XA. They are useful for re­l­at­ively short-lived op­er­a­tions in which lock­ing of re­sour­ces­such as threads and por­tions of data sourcesare ac­cept­able and in which ab­so­lute roll­backs make sense. WS-Trans­ac­tion provides a means of link­ing pro­pri­et­ary XA im­ple­ment­a­tions, in­clud­ing sup­port for two-phase commit, to Web ser­vices.

                                                                                                                                                                                                                                                业务活动通常是长期进程,可能由许多原子事务组成。如果业务活动中出现单一故障情况,则通常不希望进行全局回滚;相反,业务活动中一个原子事务的失败通常应该触发另一组服务调用和消息交换。这些交换可能包括补偿技术,以保留部分业务活动历史的方式从错误中恢复。一个示例是业务活动,包括用户在两三天内预订航班、租车、预订酒店房间以及预订剧院门票。该流程中的每个活动都可能是一个原子事务,

                                                                                                                                                                                                                                                A busi­ness activ­ity is typ­ic­ally a long-lived pro­cess that may con­sist of a number of atomic trans­ac­tions. A global roll­back is usu­ally not de­sir­able in the event of a single fail­ure con­di­tion in a busi­ness activ­ity; in­stead, a fail­ure of one atomic trans­ac­tion within a busi­ness activ­ity should often trig­ger an­other set of ser­vice in­voc­a­tions and mes­sage ex­changes. These ex­changes might in­clude com­pens­a­tion tech­niques to re­cover from the error in a way that pre­serves part of the his­tory of the busi­ness activ­ity. An ex­ample is a busi­ness activ­ity that in­cludes the book­ing of an air­line flight, rental of a car, re­ser­va­tion of a hotel room, and re­ser­va­tion of theater tick­ets that a user makes over a two- or three-day period. Each activ­ity in this flow may be an atomic trans­ac­tion, and if one of the events should fail, the over­arch­ing busi­ness activ­ity of making the travel ar­range­ments should adjust via com­pens­a­tion rather than through co­ordin­ated roll­back of all atomic trans­ac­tions.

                                                                                                                                                                                                                                                WS-Coordination 和 WS-Transaction 规范有望在服务消息传递期间发生故障时提供可靠、丰富的行为。当他们开始进入产品时,他们应该减轻开发人员的负担,围绕基于消息的服务应用程序打造自己的可事务性和上下文服务。

                                                                                                                                                                                                                                                The WS-Co­ordin­a­tion and WS-Trans­ac­tion spe­cific­a­tions are prom­ising at­tempts to provide re­li­able, rich be­ha­vi­ors in the event of fail­ures during ser­vice mes­saging. As they begin to wind their way into products, they should take the burden off of de­ve­lopers to craft their own trans­act­ab­il­ity and con­tex­tual ser­vices around mes­sage-based ser­vice ap­plic­a­tions.

                                                                                                                                                                                                                                                有关 WS-Transaction 的信息可以在http://dev2dev.bea.com/technologies/webservices/ws-transaction.jsp找到。

                                                                                                                                                                                                                                                In­form­a­tion on WS-Trans­ac­tion can be found at http://dev2dev.bea.com/tech­no­lo­gies/web­ser­vices/ws-trans­ac­tion.jsp.

                                                                                                                                                                                                                                                WS-可靠性和 WS-ReliableMessaging

                                                                                                                                                                                                                                                Web 服务之间基于消息的交互通常需要可靠、可保证的消息传递,即使在网络、应用程序或组件发生故障时也是如此,并且需要包含持久性机制和重新发送语义。然而,最流行的 Web 服务技术并不提供这种可靠性。例如,没有增强功能的 SOAP 在许多企业消息传递场景中并不全面有用,因为它最流行的绑定不能可靠地保证消息传递。

                                                                                                                                                                                                                                                It is common for mes­sage-based in­ter­ac­tions between Web ser­vices to re­quire re­li­able, guar­an­tee­able mes­saging even in the event of net­work, ap­plic­a­tion, or com­pon­ent fail­ures and to in­clude per­sist­ence mech­an­isms and resend se­mantics. The most pop­u­lar Web ser­vices tech­no­lo­gies do not provide such re­li­ab­il­ity, how­ever; SOAP without en­hance­ments, for ex­ample, isn't com­pre­hens­ively useful in many en­ter­prise mes­saging scen­arios be­cause its most pop­u­lar bind­ings don't re­li­ably guar­an­tee mes­sage de­liv­ery.

                                                                                                                                                                                                                                                为了解决这个问题,应用程序开发人员通常被迫使用 Web 服务机制(例如 SOAP 标头)的可扩展性来实现可靠性。为了消除应用程序开发人员承担此任务的需要,正在出现新的标准来解决 Web 服务的可靠性问题。两个这样的规范是 Web 服务可靠性 (WS-Reliability) 和 Web 服务可靠消息传递 (WS-ReliableMessaging)。正如新兴的 Web 服务标准领域中常见的那样,这两个标准是相互竞争的,并由针对同一问题领域的不同供应商组支持。

                                                                                                                                                                                                                                                To remedy this, ap­plic­a­tion de­ve­lopers are typ­ic­ally forced to im­ple­ment re­li­ab­il­ity them­selves using the ex­tens­ib­il­ity of Web ser­vices mech­an­isms such as SOAP Head­ers. To elim­in­ate the need for ap­plic­a­tion de­ve­lopers to take on this task, new stand­ards are emer­ging to tackle Web ser­vices re­li­ab­il­ity. Two such spe­cific­a­tions are Web Ser­vices Re­li­ab­il­ity (WS-Re­li­ab­il­ity) and Web Ser­vices Re­li­able Mes­saging (WS-Re­li­ableMes­saging). As is common in the nas­cent Web ser­vices stand­ards space, these two stand­ards are com­pet­ing, backed by dif­fer­ent groups of vendors aimed at the same prob­lem domain.

                                                                                                                                                                                                                                                WS-Reliability 为基于 SOAP 的 Web 服务提供了异步交换消息的能力,并且保证交付、无重复且消息排序。它是用于管理消息聚合和排序的 SOAP 标准,并提供用于实现保证交付和重新排序器模式等的标准策略。WS-Reliability 利用 SOAP 标头机制来添加标头元素MessageHeader 、 ReliableMessage 、 MessageOrderRMResponse到 SOAP 消息。这些元素表示消息标识符,例如组 ID 和序列号、时间戳、生存时间值、消息类型值、发送者和接收者信息以及确认回调信息。WS-Reliability由Sun、Oracle、Sonic等多家厂商开发,并已提交给OASIS。它很大程度上受到 ebMS 功能的影响。

                                                                                                                                                                                                                                                WS-Re­li­ab­il­ity provides SOAP-based Web ser­vices with the abil­ity to ex­change mes­sages asyn­chron­ously with guar­an­teed de­liv­ery, without du­plic­ates, and with mes­sage or­der­ing. It is a SOAP stand­ard for man­aging mes­sage ag­greg­a­tion and se­quen­cing, and provides a stand­ard tactic for im­ple­ment­ing the Guar­an­teed De­liv­ery and Resequen­cer pat­terns, among others. WS-Re­li­ab­il­ity lever­ages the SOAP Header mech­an­ism to add the header ele­ments Mes­sage­Header, Re­li­ableMes­sage, Mes­sageOrder, and RM­Re­sponse to SOAP mes­sages. These ele­ments denote mes­sage iden­ti­fi­ers such as group IDs and se­quence num­bers, timestamps, time-to-live values, mes­sage type values, sender and re­ceiver in­form­a­tion, and ac­know­ledg­ment call­back in­form­a­tion. WS-Re­li­ab­il­ity is pro­duced by a number of vendors, in­clud­ing Sun, Oracle, and Sonic, and it has been sub­mit­ted to OASIS. It is heav­ily in­flu­enced by the func­tion­al­ity of the ebMS.

                                                                                                                                                                                                                                                为了符合 WS-Reliability,SOAP 消息的接收方必须使用SOAP 标头中的<RMResponse>元素来响应错误或确认。如果没有收到这样的确认,则发送方使用相同的消息标识符重新发送相同的消息。发送方需要保留消息,直到其生存时间值到期或发生确认或失败;接收方还需要将消息持久化,直到它能够可靠地传输到应用层。

                                                                                                                                                                                                                                                To con­form to WS-Re­li­ab­il­ity, the re­ceiver of a SOAP mes­sage must re­spond with either a fault or an ac­know­ledg­ment using the <RM­Re­sponse> ele­ment in a SOAP header. If such an ac­know­ledg­ment is not re­ceived, then the sender re­sends the same mes­sage using the same mes­sage iden­ti­fier. The sender is re­quired to per­sist the mes­sage until its time-to-live value has ex­pired or until ac­know­ledg­ment or fail­ure has oc­curred; the re­ceiver is also re­quired to per­sist the mes­sage until it can be re­li­ably trans­mit­ted to the ap­plic­a­tion layer.

                                                                                                                                                                                                                                                为了确保强制执行一次且仅一次的消息行为,WS-Reliability 提供了一种可以根据应用程序需求启用的序列号机制。组合在一起的单独消息可以共享相同的组标识符,但会在 SOAP 标头中通告它们自己的序列号,从而允许接收者在将消息传递到应用程序之前对消息进行重新排序。

                                                                                                                                                                                                                                                To ensure that once-and-only-once mes­sage be­ha­vi­ors are en­forced, WS-Re­li­ab­il­ity provides a se­quence number mech­an­ism that can be en­abled based on ap­plic­a­tion re­quire­ments. Sep­ar­ate mes­sages that are grouped to­gether may share the same group iden­ti­fier but will ad­vert­ise their own se­quence num­bers within the SOAP header, al­low­ing re­ceiv­ers to resequence the mes­sages before de­liv­er­ing them to the ap­plic­a­tion.

                                                                                                                                                                                                                                                WS-Reliability 规范可以在http://www.oasis-open.org/committees/documents.php?wg_abbrev=wsrm中找到。

                                                                                                                                                                                                                                                The WS-Re­li­ab­il­ity spe­cific­a­tion can be found at http://www.oasis-open.org/com­mit­tees/doc­u­ments.php?wg_ab­brev=wsrm.

                                                                                                                                                                                                                                                WS-ReliableMessaging 规范描述了一个类似的协议,该协议允许在出现故障时在分布式应用程序之间可靠地传递消息。该协议以独立的方式描述,允许使用各种网络传输技术和绑定来实现。该规范确实包含一种针对 SOAP 的特定绑定。WS-ReliableMessaging 得到了 BEA、IBM 和 Microsoft 的支持,但尚未发布给标准机构。

                                                                                                                                                                                                                                                The WS-Re­li­ableMes­saging spe­cific­a­tion de­scribes a sim­ilar pro­tocol that allows mes­sages to be de­livered re­li­ably between dis­trib­uted ap­plic­a­tions in the pres­ence of fail­ures. The pro­tocol is de­scribed in an in­de­pend­ent manner, al­low­ing it to be im­ple­men­ted using a vari­ety of net­work trans­port tech­no­lo­gies and bind­ings. The spe­cific­a­tion does in­clude one spe­cific bind­ing for SOAP. WS-Re­li­ableMes­saging is backed by BEA, IBM, and Mi­crosoft, and has not yet been re­leased to a stand­ards body.

                                                                                                                                                                                                                                                WS-ReliableMessaging 的运行原理与 WS-Reliability 相同。它对确认、回调和标识符进行了类似的使用;意味着持久消息缓存的类似用法;并提供详细的故障信息。它确保消息按照四种基本传送保证中的任意一种进行传送:最多一次、至少一次、恰好一次和按顺序。下面说明了通过 WS-ReliableMessaging 传递的消息序列的示例。

                                                                                                                                                                                                                                                WS-Re­li­ableMes­saging op­er­ates on the same prin­ciples as WS-Re­li­ab­il­ity. It makes sim­ilar use of ac­know­ledg­ments, call­backs, and iden­ti­fi­ers; im­plies sim­ilar usage of per­sist­ent mes­sage caches; and offers de­tailed fault mes­sages. It en­sures that mes­sages are de­livered ac­cord­ing to any of four basic de­liv­ery as­sur­ances: At most once, at least once, ex­actly once, and in order. An ex­ample of a mes­sage se­quence de­livered through WS-Re­li­ableMes­saging is il­lus­trated below.

                                                                                                                                                                                                                                                WS-ReliableMessaging 通过一系列依赖于插入 SOAP 标头元素中的唯一 ID 的确认回调来提供有保证的顺序传送

                                                                                                                                                                                                                                                WS-Re­li­ableMes­saging Provides Guar­an­teed Se­quen­tial De­liv­ery through a Series of Ac­know­ledg­ment Call­backs That Rely on Unique IDs In­ser­ted into the SOAP Header Ele­ment

                                                                                                                                                                                                                                                图形/14inf03.gif

                                                                                                                                                                                                                                                这两个相互竞争的可靠性规范之间的一个主要区别是 WS-ReliableMessaging 包括使用其他关键 Web 服务规范,例如 WS-Security 和 WS-Addressing。实际上,这意味着 WS-ReliableMessaging 使用其他标准中的特定惯用语来提供此信息,而 WS-Reliability 支持这些功能,但尚未坚持其他新标准中指定的惯用形式。

                                                                                                                                                                                                                                                A key dif­fer­ence between the two com­pet­ing re­li­ab­il­ity spe­cific­a­tions is that WS-Re­li­ableMes­saging in­cludes usage of other crit­ical Web ser­vices spe­cific­a­tions, such as WS-Se­cur­ity and WS-Ad­dress­ing. In prac­tice, this means that WS-Re­li­ableMes­saging uses spe­cific idioms from other stand­ards for sup­ply­ing this in­form­a­tion, whereas WS-Re­li­ab­il­ity sup­ports the fea­tures but does not yet insist on the idio­matic forms spe­cified in the other new stand­ards.

                                                                                                                                                                                                                                                有关 WS-ReliableMessaging 的更多信息,请访问http://dev2dev.bea.com/technologies/webservices/ws-reliablemessaging.jsp

                                                                                                                                                                                                                                                More in­form­a­tion on WS-Re­li­ableMes­saging can be found at http://dev2dev.bea.com/tech­no­lo­gies/web­ser­vices/ws-re­li­ablemes­saging.jsp.

                                                                                                                                                                                                                                                WS-对话

                                                                                                                                                                                                                                                Web 服务会话 (WS-Conversation) 指定用于管理发送方和接收方之间(通常跨两个 SOAP 端点)的有状态异步消息交换的协议。与依靠封装业务流程组件来管理多个合作伙伴之间的有状态消息交换相比,该提议提供了一种在单个客户端和单个服务(其中客户端也可能是服务)之间完成相同事情的简单方法。

                                                                                                                                                                                                                                                Web Ser­vices Con­ver­sa­tion (WS-Con­ver­sa­tion) spe­cifies a pro­tocol for man­aging state­ful asyn­chron­ous mes­sage ex­change between a sender and re­ceiver, usu­ally across two SOAP en­d­points. In con­trast to re­ly­ing on an en­cap­su­lat­ing busi­ness pro­cess com­pon­ent for man­aging state­ful mes­sage ex­change among mul­tiple part­ners, this pro­posal provides a simple means of ac­com­plish­ing the same thing between a single client and single ser­vice (where the client may also be a ser­vice).

                                                                                                                                                                                                                                                该协议利用 SOAP 标头机制将标识符或令牌 ID 与 SOAP 消息一起发送。当有状态对话开始时,StartHeader元素用于提供对话标识符和回调 URI。作为同一对话一部分的后续消息使用ContinueHeader和CallbackHeader 来进行进一步的请求和回复,并且这些元素将包含在对话开始时建立的相同标识符。

                                                                                                                                                                                                                                                The pro­tocol lever­ages the SOAP Header mech­an­ism to send iden­ti­fi­ers, or token IDs, along with SOAP mes­sages. When a state­ful con­ver­sa­tion is begun, the StartHeader ele­ment is used to supply a con­ver­sa­tion iden­ti­fier and call­back URI. Sub­se­quent mes­sages that are part of the same con­ver­sa­tion use the Con­tin­ue­Header and the Call­back­Header for fur­ther re­quests and replies, and these ele­ments will in­clude the same iden­ti­fier es­tab­lished when the con­ver­sa­tion was star­ted.

                                                                                                                                                                                                                                                SOAP 标头机制可用于实现这些模式,而无需使用这些标准机制,尽管为跨平台的客户端和服务建立通用机制有好处。它是聚合器和组合消息处理器模式的形式化,使用相关标识符来将多个消息映射到单个会话。

                                                                                                                                                                                                                                                The SOAP Header mech­an­ism can be used to im­ple­ment these pat­terns without use of these stand­ard mech­an­isms, though there is be­ne­fit in es­tab­lish­ing a common mech­an­ism for cli­ents and ser­vices across plat­forms. It is a form­al­iz­a­tion of the Ag­greg­ator and Com­posed Mes­sage Pro­cessor pat­terns using Cor­rel­a­tion Iden­ti­fier for the pur­poses of map­ping mul­tiple mes­sages to a single ses­sion.

                                                                                                                                                                                                                                                通过流程组件或编排来外部化状态管理的方法对于更大规模的集成项目来说似乎更有前景,为了简单起见,将服务和组件视为黑匣子是有用的,但是 WS-Conversation 是一个可以在以下情况下完成的示例:可以更好地控制服务和客户。

                                                                                                                                                                                                                                                The ap­proach of ex­tern­al­iz­ing state man­age­ment through a pro­cess com­pon­ent or cho­reo­graphy seems more prom­ising for larger scale in­teg­ra­tion pro­jects where it is useful for sim­pli­city's sake to con­sider the ser­vices and com­pon­ents as black boxes, but WS-Con­ver­sa­tion is an ex­ample of what can be done when greater con­trol over the ser­vices and cli­ents is pos­sible.

                                                                                                                                                                                                                                                WS-安全

                                                                                                                                                                                                                                                虽然 WS-ReliableMessaging、WS-Coordination 和 WS-Addressing 等标准提供了各种识别消息发送者的机制,但这些规范都不能保证发送者实际上拥有其声称的身份。WS-I Basic ProfileXML Schema、SOAP、WSDL 和 UDDI 的元素没有提及如何验证和保证身份或如何维护消息完整性。Web 服务的早期采用者要么保持其服务开放并可供所有人使用,要么开发专有的安全协议来填补这一空白。专有和私有方法在消息发送者和接收者之间造成了不良耦合,这在异步和松散耦合的消息节点联合中尤其令人烦恼。

                                                                                                                                                                                                                                                While stand­ards such as WS-Re­li­ableMes­saging, WS-Co­ordin­a­tion, and WS-Ad­dress­ing provide vari­ous mech­an­isms that identify the sender of a mes­sage, none of those spe­cific­a­tions guar­an­tee that the sender ac­tu­ally owns the iden­tity it claims. The ele­ments of the WS-I Basic Pro­fileXML Schema, SOAP, WSDL, and UD­DI­make no men­tion of how iden­tity is veri­fied and guar­an­teed or how mes­sage in­teg­rity is to be main­tained. Early ad­op­ters of Web ser­vices either kept their ser­vices open and avail­able to all or de­ve­loped pro­pri­et­ary se­cur­ity pro­to­cols in order to fill this gap. The pro­pri­et­ary and private ap­proaches cre­ated an un­desir­able coup­ling between mes­sage senders and re­ceiv­ers, some­thing par­tic­u­larly galling in oth­er­wise asyn­chron­ous and loosely coupled fed­er­a­tions of mes­sage nodes.

                                                                                                                                                                                                                                                WS-Security,也称为Web 服务安全语言,是解决这些问题的建议标准方法。在所有 WS-* 规范中,它可能在主要 Web 服务平台供应商中享有最广泛的共识。它由 Microsoft、IBM 和 Verisign 组成的联盟创建,现在是 OASIS 的一部分;它已获得 Sun、BEA、Intel、SAP、IONA、RSA Security 等公司的信任票。

                                                                                                                                                                                                                                                WS-Se­cur­ity, also re­ferred to as the Web Ser­vices Se­cur­ity Lan­guage, is the pro­posed stand­ard means of ad­dress­ing these issues. Of all the WS-* spe­cific­a­tions, it enjoys per­haps the broad­est level of con­sensus among the major Web ser­vices plat­form vendors. Cre­ated by a con­sor­tium con­sist­ing of Mi­crosoft, IBM, and Ver­isign, it is now part of OASIS; it has re­ceived votes of con­fid­ence from the likes of Sun, BEA, Intel, SAP, IONA, RSA Se­cur­ity, and others.

                                                                                                                                                                                                                                                WS-Security 并不提出新的安全技术,而是将 SOAP 与现有的安全技术联系起来。它提供了一种通用的、可扩展的方法,将安全令牌与 SOAP 消息关联起来,并将这些令牌传播到 SOAP 端点。它还指定了在 SOAP 消息中编码二进制安全令牌(例如数字证书和 Kerberos 票证)的标准方法。它不描述特定的固定协议,而是建立一套通用机制,用于实现任意数量的安全协议并合并任意数量的信任域、签名格式和加密技术。它尽可能包括数字证书、摘要的使用以及 PKI、Kerberos、SSL 等技术的实现,

                                                                                                                                                                                                                                                WS-Se­cur­ity does not pro­pose new se­cur­ity tech­no­lo­gies, but rather it bridges SOAP and ex­ist­ing se­cur­ity tech­no­lo­gies. It provides a gen­eric, ex­tens­ible means of as­so­ci­at­ing se­cur­ity tokens with SOAP mes­sages and propagat­ing those tokens to SOAP en­d­points. It also spe­cifies the stand­ard ap­proach to en­cod­ing binary se­cur­ity tokens, such as di­gital cer­ti­fic­ates and Ker­beros tick­ets, in SOAP mes­sages. It does not de­scribe spe­cific fixed pro­to­cols but rather es­tab­lishes a gen­eral set of mech­an­isms for im­ple­ment­ing any number of se­cur­ity pro­to­cols and in­cor­por­at­ing any number of trust do­mains, sig­na­ture formats, and en­cryp­tion tech­no­lo­gies. In as much as it can in­clude use of di­gital cer­ti­fic­ates, di­gests, and im­ple­ment­a­tions of tech­no­lo­gies such as PKI, Ker­beros, SSL, and the like, WS-Se­cur­ity rep­res­ents a way to apply fa­mil­iar In­ter­net se­cur­ity stand­bys to SOAP en­d­points.

                                                                                                                                                                                                                                                除了验证发送者身份之外,WS-Security 还可以利用 W3C XML 签名和 XML 加密标准来保护消息完整性,即在网络传输过程中保护消息免受中间人的窥探。然而,在不涉及中间参与者和第三方服务的情况下,使用 HTTPS 是保护传输中的 SOAP 消息的常见替代方法。

                                                                                                                                                                                                                                                In ad­di­tion to veri­fic­a­tion of sender iden­tity, WS-Se­cur­ity may pro­tect mes­sage in­teg­ritythat is, pro­tect it from in­ter­me­di­ary prying eyes during net­work trans­mis­sionby lever­aging the W3C XML Sig­na­ture and XML En­cryp­tion stand­ards. In cases in which in­ter­me­di­ary actors and third-party ser­vices are not in­volved, how­ever, the use of HTTPS is a common al­tern­at­ive means of pro­tect­ing SOAP mes­sages in transit.

                                                                                                                                                                                                                                                该安全模型指定 SOAP 消息发送者提出一系列声明,例如发送者的身份、组、特权等。这些声明以签名安全令牌的形式收集。消息的接收者负责认可该声明。令牌和签名在 SOAP 标头块中携带,特别是在安全性下WS-Security 命名空间中的标头元素。如果接收方认可声明时发生错误,则该错误将是以下两种类型之一:不支持的错误,这表明端点不支持特定的令牌或加密算法;以及失败错误,这表明大多数其他错误,包括所有这些错误与无效令牌和签名相关。该规范并不强制要求始终报告故障错误,因为它们可能是攻击的结果。报告错误时,它们采用 SOAP 错误的形式,并带有规范中定义的错误代码。

                                                                                                                                                                                                                                                The se­cur­ity model spe­cifies that a SOAP mes­sage sender makes a series of claims such as the sender's iden­tity, group, priv­ilege, and the like. These claims are col­lec­ted in the form of a signed se­cur­ity token. The re­ceiver of the mes­sage is charged with en­dors­ing the claims. The tokens and sig­na­tures are car­ried in a SOAP Header block, spe­cific­ally under the se­cur­ity header ele­ment in the WS-Se­cur­ity namespace. If an error occurs when the re­ceiver en­dorses claims, that error will be one of two types: un­sup­por­ted errors, which in­dic­ate that the en­d­point does not sup­port a par­tic­u­lar token or en­cryp­tion al­gorithm, and fail­ure errors, which in­dic­ate most other errors, in­clud­ing all those re­lated to in­valid tokens and sig­na­tures. The spe­cific­a­tion does not man­date that fail­ure errors always be re­por­ted, as they may be the result of an attack. When errors are re­por­ted, they take the form of SOAP Faults with fault codes defined in the spe­cific­a­tion.

                                                                                                                                                                                                                                                WS-Security 规范可以在每个共同创作供应商的网站上找到,包括 IBM 的以下位置:http://www-106.ibm.com/developerworks/library/ws-secure/

                                                                                                                                                                                                                                                The WS-Se­cur­ity spe­cific­a­tion can be found on the Web sites of each of its co-au­thor­ing vendors, in­clud­ing the fol­low­ing loc­a­tion at IBM: http://www-106.ibm.com/de­ve­loper­works/lib­rary/ws-secure/.

                                                                                                                                                                                                                                                WS-Addressing、WS-Policy 和其他 WS-* 规范

                                                                                                                                                                                                                                                还有许多其他拟议的 WS-* 规范,得到了不同程度的支持和接受。有些范围相当狭窄,例如 WS-Addressing,它定义 XML 元素来标识消息中的 Web 服务端点。该规范旨在以传输中立的方式支持通过端点管理器、代理、防火墙和网关等中介进行消息传递。本质上,WS-Addressing 是一种标准方法,用于指示消息的来源(“发件人:”)和收件人(“收件人:”)。它提供了一种将基于 SOAP 的 Web 服务插入到收件人列表解决方案中的方法。由于它提供了一种指定将回复定向到何处的方法,因此它似乎注定会成为解决 中提出的问题的标准 Web 服务方法。退货地址

                                                                                                                                                                                                                                                There are a number of other pro­posed WS-* spe­cific­a­tions with vary­ing de­grees of sup­port and ac­cept­ance. Some are quite narrow in scope, such as WS-Ad­dress­ing, which defines XML ele­ments to identify Web ser­vices en­d­points in mes­sages. This spe­cific­a­tion aims to sup­port mes­saging through in­ter­me­di­ar­ies such as en­d­point man­agers, prox­ies, fire­walls, and gate­ways in a trans­port-neut­ral manner. Es­sen­tially, WS-Ad­dress­ing is a stand­ard way to denote who a mes­sage is from ("From:") and who it is to ("To:"). It provides a means to plug SOAP-based Web ser­vices into Re­cip­i­ent List solu­tions. As it provides a means of spe­cify­ing where to direct a reply, it ap­pears destined to be the stand­ard Web ser­vices ap­proach to resolv­ing the issues raised in Return Ad­dress.

                                                                                                                                                                                                                                                Web 服务策略框架 (WS-Policy) 是一种提供有关服务的元数据的方法,它提供了用于描述 Web 服务策略的语法。这些策略包括服务需求、偏好、功能和服务质量元数据。随附的 Web 服务策略断言语言 (WS-PolicyAssertions) 指定了断言消息或服务端点支持特定策略的方法。它涉及调查 WS-Policy 声明以寻找特定的所需策略。最后,Web 服务策略附件 (WS-PolicyAttachment) 描述了这些策略标准如何适应现有的 Web 服务技术。它指定如何将策略表达式与 WSDL 类型定义和 UDDI 实体相关联,

                                                                                                                                                                                                                                                A means of sup­ply­ing metadata about ser­vices, the Web Ser­vices Policy Frame­work (WS-Policy) offers a syntax for de­scrib­ing the policies of a Web ser­vice. Such policies in­clude ser­vice re­quire­ments, pref­er­ences, cap­ab­il­it­ies, and qual­ity of ser­vice metadata. The ac­com­pa­ny­ing Web Ser­vices Policy As­ser­tions Lan­guage (WS-Poli­cy­Asser­tions) spe­cifies a means of as­sert­ing that a mes­sage or ser­vice en­d­point sup­ports a par­tic­u­lar policy. It in­volves in­vest­ig­at­ing the WS-Policy de­clar­a­tions in search of a spe­cific re­quired policy. Fi­nally, the Web Ser­vices Policy At­tach­ment (WS-Poli­cy­At­tach­ment) de­scribes how these policy stand­ards fit into ex­ist­ing Web ser­vices tech­no­lo­gies. It spe­cifies how to as­so­ci­ate policy ex­pres­sions with WSDL type defin­i­tions and UDDI en­tit­ies, and it defines how to as­so­ci­ate im­ple­ment­a­tion-spe­cific policies with all or part of a WSDL port­Type.

                                                                                                                                                                                                                                                随着供应商竞相规范应用程序开发人员所遵循的实施实践,并在此过程中争取有价值的 Web 服务知识产权,新的 WS-* 规范正在迅速出现。明智的应用程序开发人员会关注有趣的标准,并在有用时从中剔除习惯用法,但也会记住,对于构建基于消息的应用程序来说,最重要的是模式而不是实现习惯用法。新兴的 WS-* 标准还不是实现可互操作的企业消息传递系统所必需的,如果它们成为障碍或干扰,则应大胆地忽略它们,直到它们成熟为止。

                                                                                                                                                                                                                                                New WS-* spe­cific­a­tions are sur­fa­cing quickly as vendors race to form­al­ize the im­ple­ment­a­tion prac­tices that ap­plic­a­tion de­ve­lopers are fol­low­ing, stak­ing a claim to valu­able Web ser­vices in­tel­lec­tual prop­erty in the pro­cess. Wise ap­plic­a­tion de­ve­lopers will keep an eye on in­ter­est­ing stand­ards and cull idioms from them when useful, but will also bear in mind that the pat­tern and not the im­ple­ment­a­tion idiom is most im­port­ant to craft­ing a mes­sage-based ap­plic­a­tion. The emer­ging WS-* stand­ards are not yet re­quired for im­ple­ment­ing in­ter­op­er­able en­ter­prise mes­saging sys­tems, and where they become hindrances or dis­trac­tions, they should boldly be ig­nored until mature.

                                                                                                                                                                                                                                                结论

                                                                                                                                                                                                                                                Con­clu­sions

                                                                                                                                                                                                                                                在最好的情况下,标准通过确保同一模式的不同实现可以互操作来扩展设计模式的范围。人们正在进行许多努力来通过 Web 服务标准来扩展消息传递模式;其中许多标准侧重于称为业务流程组件的工作流组件的组成和行为。BPEL、WSCI 和 WS-* 规范等标准解决了本书中描述的许多问题,并用这种模式语言实现了一些模式。

                                                                                                                                                                                                                                                At their best, stand­ards extend the reach of design pat­terns by en­sur­ing that dif­fer­ent im­ple­ment­a­tions of the same pat­tern are in­ter­op­er­able. Many ef­forts are un­der­way to extend mes­saging pat­terns through Web ser­vices stand­ards; many of these stand­ards focus on the com­pos­i­tion and be­ha­vior of work­flow com­pon­ents called busi­ness pro­cess com­pon­ents. Stand­ards such as BPEL, WSCI, and the WS-* spe­cific­a­tions tackle many of the prob­lems de­scribed in this book and im­ple­ment sev­eral of the pat­terns in this pat­tern lan­guage.

                                                                                                                                                                                                                                                尽管如此,新兴标准的故事有时会发生冲突并且可能令人困惑。他们当然还没有准备好迎接黄金时段。应用模式时,应用程序开发人员应避免陷入标准困境,而应专注于手头的特定用例。看看某些标准是如何解决问题的,如果有意义或者在实施过程中是否有帮助,就采用这些战术性、惯用的模式实现,无论该方法是否跨供应商产品标准化。开发人员还可以向标准组织提供反馈,以提出质疑、批评或以其他方式确保标准变得实用,而不是学术或供应商的练习。随着标准的成熟,

                                                                                                                                                                                                                                                Still, the emer­ging stand­ards stor­ies are oc­ca­sion­ally con­flict­ing and can be con­fus­ing. They're cer­tainly not all ready for prime time. When ap­ply­ing a pat­tern, an ap­plic­a­tion de­ve­loper should avoid get­ting bogged down by stand­ards and stay fo­cused in­stead on the par­tic­u­lar use cases at hand. Take a look at how cer­tain stand­ards are ap­proach­ing a prob­lem, and adopt those tac­tical, idio­matic im­ple­ment­a­tions of a pat­tern if it makes sense or if it's help­ful during im­ple­ment­a­tion­regard­less of whether the ap­proach is stand­ard­ized across vendor products. De­ve­lopers can also provide feed­back to stand­ards or­gan­iz­a­tions to chal­lenge, cri­ti­cize, and oth­er­wise ensure that stand­ards become prac­tic­ally useful and not aca­demic or vendor ex­er­cises. As stand­ards mature, ar­chi­tects are wise to con­sider how their use might extend en­ter­prise in­teg­ra­tion solu­tions to broader, less risky, less costly, and more power­ful levels of soph­ist­ic­a­tion.

                                                                                                                                                                                                                                                  参考书目

                                                                                                                                                                                                                                                  Bibliography

                                                                                                                                                                                                                                                  [亚历山大]

                                                                                                                                                                                                                                                  模式语言:城镇、建筑、建筑, Christopher Alexander、Sara Ishikawa 和 Murray Silverstein;牛津大学出版社 1977 年,ISBN 0195019199

                                                                                                                                                                                                                                                  可能是所有与模式相关的著作中被引用最多的书。实际上,我使用了亚历山大的一些图案来为室内建筑课程设计一座海滨别墅。有趣的是,亚历山大将建筑和设计剖析成一组可组合结构的愿望可能源于他对数学的研究。他的论文发表为“关于形式综合的注释”,引用了他用 IBM 7090 汇编代码开发的一组程序。所以,

                                                                                                                                                                                                                                                  [Al­ex­an­der]

                                                                                                                                                                                                                                                  A Pat­tern Lan­guage: Towns, Build­ings, Con­struc­tion, Chris­topher Al­ex­an­der, Sara Ishi­kawa, & Murray Sil­ver­stein; Oxford Uni­ver­sity Press 1977, ISBN 0195019199

                                                                                                                                                                                                                                                  Prob­ably the most quoted book in any work re­lated to pat­terns. I ac­tu­ally used a number of Al­ex­an­der's pat­terns to design a beach house for an in­terior ar­chi­tec­ture class. It is in­ter­est­ing to note that Al­ex­an­der's desire to dis­sect ar­chi­tec­ture and design into a set of com­pos­able con­structs may stem from his study of math­em­at­ics. His thesis, pub­lished as "Notes on the syn­thesis of form," ref­er­ences a set of pro­grams that he de­ve­loped in IBM 7090 as­sembly code. So, the tre­mend­ous suc­cess of Al­ex­an­der's pat­terns in the soft­ware com­munity is not quite co­in­cid­ental.

                                                                                                                                                                                                                                                  [Alpert]

                                                                                                                                                                                                                                                  设计模式 Smalltalk Companion, Sherman Alpert、Kyle Brown 和 Bobby Woolf;Addison-Wesley 1998,ISBN 0201184621

                                                                                                                                                                                                                                                  再看一下设计模式材料[GoF],这次是针对使用具有公共类库并在具有垃圾收集功能的虚拟机上运行的编程环境的开发人员。当时,这是 Smalltalk,但许多见解也适用于 Java 和 .NET/C#。

                                                                                                                                                                                                                                                  [Alpert]

                                                                                                                                                                                                                                                  The Design Pat­terns Small­talk Com­pan­ion, Sher­man Alpert, Kyle Brown, & Bobby Woolf; Ad­dison-Wesley 1998, ISBN 0201184621

                                                                                                                                                                                                                                                  A second look at the Design Pat­terns ma­ter­ial [GoF], this time for de­ve­lopers using a pro­gram­ming en­vir­on­ment that has a common class lib­rary and runs on a vir­tual ma­chine with garbage col­lec­tion. At the time, that was Small­talk, but many of the in­sights also apply to Java and .NET/C#.

                                                                                                                                                                                                                                                  [Box]

                                                                                                                                                                                                                                                  Essential .NET,第 1 卷:公共语言运行时,Don Box;Addison-Wesley 2002,ISBN 0201734117

                                                                                                                                                                                                                                                  关于 CLR 内部工作原理的了解超乎您的想​​象。

                                                                                                                                                                                                                                                  [Box]

                                                                                                                                                                                                                                                  Es­sen­tial .NET, Volume 1: The Common Lan­guage Runtime, Don Box; Ad­dison-Wesley 2002, ISBN 0201734117

                                                                                                                                                                                                                                                  More than you ever wanted to know about the inner work­ings of the CLR.

                                                                                                                                                                                                                                                  [BPEL4WS]

                                                                                                                                                                                                                                                  Web 服务业务流程执行语言,版本 1.0,BEA、IBM、Microsoft;2002 年 7 月 31 日,http://www.ibm.com/developerworks/webservices/library/ws-bpel1/

                                                                                                                                                                                                                                                  BPEL4WS 1.0 规范。

                                                                                                                                                                                                                                                  [BPEL4WS]

                                                                                                                                                                                                                                                  Busi­ness Pro­cess Ex­e­cu­tion Lan­guage for Web Ser­vices, Ver­sion 1.0, BEA, IBM, Mi­crosoft; July 31, 2002, http://www.ibm.com/de­ve­loper­works/web­ser­vices/lib­rary/ws-bpel1/

                                                                                                                                                                                                                                                  The BPEL4WS 1.0 spe­cific­a­tion.

                                                                                                                                                                                                                                                  [CoreJ2EE]

                                                                                                                                                                                                                                                  核心 J2EE 模式:最佳实践和设计策略(第二版),Deepak Alur、John Crupi 和 Dan Malks;普伦蒂斯·霍尔 PTR 2003,ISBN 0131422464

                                                                                                                                                                                                                                                  一本关于 Java 企业应用程序架构模式的非常好的书。

                                                                                                                                                                                                                                                  [Core­J2EE]

                                                                                                                                                                                                                                                  Core J2EE Pat­terns: Best Prac­tices and Design Strategies (2nd edi­tion), Deepak Alur, John Crupi, & Dan Malks; Pren­tice Hall PTR 2003, ISBN 0131422464

                                                                                                                                                                                                                                                  A very good book on en­ter­prise ap­plic­a­tion ar­chi­tec­ture pat­terns for Java.

                                                                                                                                                                                                                                                  [CSP]

                                                                                                                                                                                                                                                  “通信顺序过程”, CAR Hoare;ACM 通讯,1978

                                                                                                                                                                                                                                                  ACM 在线图书馆需要访问才能查看本文的全文版本。

                                                                                                                                                                                                                                                  [CSP]

                                                                                                                                                                                                                                                  "Com­mu­nic­at­ing Se­quen­tial Pro­cesses," C. A. R. Hoare; Com­mu­nic­a­tions of the ACM, 1978

                                                                                                                                                                                                                                                  ACM online lib­rary access is re­quired to see the full-text ver­sion of this art­icle.

                                                                                                                                                                                                                                                  [Dickman]

                                                                                                                                                                                                                                                  使用 MSMQ 设计应用程序,Alan Dickman;Addison-Wesley 1998,ISBN 0201325810

                                                                                                                                                                                                                                                  这本书包含一个关于“消息传递问题的解决方案”的精彩章节,涉及相关性、事件驱动的消费者以及对象序列化和反序列化。不幸的是,本书的年代久远意味着所有示例都使用带有 COM 或 C++ 的 Visual Basic。

                                                                                                                                                                                                                                                  [Dick­man]

                                                                                                                                                                                                                                                  Design­ing Ap­plic­a­tions with MSMQ, Alan Dick­man; Ad­dison-Wesley 1998, ISBN 0201325810

                                                                                                                                                                                                                                                  The book con­tains a great chapter on "Solu­tions to Mes­saging Prob­lems" that deals with cor­rel­a­tion, event-driven con­sumers and object seri­al­iz­a­tion, and deseri­al­iz­a­tion. Un­for­tu­nately, the age of the book means that all ex­amples use Visual Basic with COM or C++.

                                                                                                                                                                                                                                                  [Douglass]

                                                                                                                                                                                                                                                  实时设计模式, Bruce Powel Douglass;Addison-Wesley 2003,ISBN 0201699567

                                                                                                                                                                                                                                                  本书证明了模式跨领域的可移植性。事实证明,道格拉斯的一些可靠性模式在企业消息传递环境中非常有用。

                                                                                                                                                                                                                                                  [Dou­glass]

                                                                                                                                                                                                                                                  Real-Time Design Pat­terns, Bruce Powel Dou­glass; Ad­dison-Wesley 2003, ISBN 0201699567

                                                                                                                                                                                                                                                  This book proves the trans­port­ab­il­ity of pat­terns across do­mains. Some of Dou­glass's re­li­ab­il­ity pat­terns prove very useful in the con­text of en­ter­prise mes­saging.

                                                                                                                                                                                                                                                  [EAA]

                                                                                                                                                                                                                                                  企业应用程序架构模式, Martin Fowler;Addison-Wesley 2003,ISBN 0321127420

                                                                                                                                                                                                                                                  迄今为止关于应用程序架构模式的最全面的书籍。尽管它涵盖了 51 种模式,但它读起来既简单又有趣,同时又不牺牲技术准确性。

                                                                                                                                                                                                                                                  [EAA]

                                                                                                                                                                                                                                                  Pat­terns of En­ter­prise Ap­plic­a­tion Ar­chi­tec­ture, Martin Fowler; Ad­dison-Wesley 2003, ISBN 0321127420

                                                                                                                                                                                                                                                  The most com­pre­hens­ive book yet on ap­plic­a­tion ar­chi­tec­ture pat­terns. Even though it covers 51 pat­terns, it is an easy and in­ter­est­ing read while never sac­ri­fi­cing tech­nical ac­cur­acy.

                                                                                                                                                                                                                                                  [EJB 2.0]

                                                                                                                                                                                                                                                  Enterprise JavaBeans 规范,版本 2.0,Sun Microsystems;2001 年 8 月 14 日,http://java.sun.com/products/ejb/docs.html

                                                                                                                                                                                                                                                  EJB 2.0 规范。

                                                                                                                                                                                                                                                  [EJB 2.0]

                                                                                                                                                                                                                                                  En­ter­prise Java­Beans Spe­cific­a­tion, Ver­sion 2.0, Sun Mi­crosys­tems; August 14, 2001, http://java.sun.com/products/ejb/docs.html

                                                                                                                                                                                                                                                  The EJB 2.0 spe­cific­a­tion.

                                                                                                                                                                                                                                                  [Garlan]

                                                                                                                                                                                                                                                  软件架构:新兴学科的视角, Mary Shaw 和 David Garlan;Prentice Hall 1996,ISBN 0131829572

                                                                                                                                                                                                                                                  这本书包含关于建筑风格的精彩章节,包括>管道和过滤器。

                                                                                                                                                                                                                                                  [Garlan]

                                                                                                                                                                                                                                                  Soft­ware Ar­chi­tec­ture: Per­spect­ives on an Emer­ging Dis­cip­line, Mary Shaw & David Garlan; Pren­tice Hall 1996, ISBN 0131829572

                                                                                                                                                                                                                                                  The book con­tains a great chapter on ar­chi­tec­tural styles, in­clud­ing >Pipes and Fil­ters.

                                                                                                                                                                                                                                                  [GoF]

                                                                                                                                                                                                                                                  设计模式:可重用面向对象软件的元素, Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides;Addison-Wesley 1995,ISBN 0201633612

                                                                                                                                                                                                                                                  无疑是所有模式著作中被引用次数第二多的书。

                                                                                                                                                                                                                                                  [GoF]

                                                                                                                                                                                                                                                  Design Pat­terns: Ele­ments of Re­usable Object-Ori­ented Soft­ware, Erich Gamma, Richard Helm, Ralph John­son, & John Vlis­sides; Ad­dison-Wesley 1995, ISBN 0201633612

                                                                                                                                                                                                                                                  Surely the second most quoted book in any work on pat­terns.

                                                                                                                                                                                                                                                  [Graham]

                                                                                                                                                                                                                                                  使用 Java 构建 Web 服务:理解 XML、SOAP 和 UDDI,Steve Graham、Simon Simeonov、Toufic Boubez、Glen Daniels、Doug Davis、Yuichi Nakamura 和 Ryo Nyeama;SAMS Publishing 2002,ISBN 0672321815

                                                                                                                                                                                                                                                  一本关于 Java 和 Web 服务如何结合在一起的非常好的书。

                                                                                                                                                                                                                                                  [Graham]

                                                                                                                                                                                                                                                  Build­ing Web Ser­vices with Java: Making Sense of XML, SOAP and UDDI, Steve Graham, Simon Simeonov, Toufic Boubez, Glen Daniels, Doug Davis, Yuichi Na­kamura, & Ryo Nyeama; SAMS Pub­lish­ing 2002, ISBN 0672321815

                                                                                                                                                                                                                                                  A very good book on how Java and Web ser­vices come to­gether.

                                                                                                                                                                                                                                                  [Hapner]

                                                                                                                                                                                                                                                  Java 消息服务 API 教程和参考, Mark Hapner、Rich Burridge、Rahul Sharma、Joseph Fialli 和 Kim Haase;Addison-Wesley 2002,ISBN 0201784726

                                                                                                                                                                                                                                                  JMS 如何工作,来自编写该规范的作者。

                                                                                                                                                                                                                                                  [Hapner]

                                                                                                                                                                                                                                                  Java Mes­saging Ser­vice API Tu­torial and Ref­er­ence, Mark Hapner, Rich Burridge, Rahul Sharma, Joseph Fialli, & Kim Haase; Ad­dison-Wesley 2002, ISBN 0201784726

                                                                                                                                                                                                                                                  How JMS works, from the au­thors who wrote the spe­cific­a­tion.

                                                                                                                                                                                                                                                  [Hohmann]

                                                                                                                                                                                                                                                  超越软件架构:创建和维持获胜解决方案, Luke Hohmann;Addison-Wesley 2003,ISBN 0201775948

                                                                                                                                                                                                                                                  Luke 提醒我们有多少架构决策并非仅由技术驱动,而是由业务决策、许可方案和许多其他外部因素驱动。

                                                                                                                                                                                                                                                  [Hohmann]

                                                                                                                                                                                                                                                  Beyond Soft­ware Ar­chi­tec­ture: Cre­at­ing and Sus­tain­ing Win­ning Solu­tions, Luke Hohmann; Ad­dison-Wesley 2003, ISBN 0201775948

                                                                                                                                                                                                                                                  Luke re­minds us how many ar­chi­tec­tural de­cisions are not driven by tech­no­logy alone but by busi­ness de­cisions, li­cens­ing schemes, and a host of other ex­ternal factors.

                                                                                                                                                                                                                                                  [JMS]

                                                                                                                                                                                                                                                  Java 消息服务 (JMS),Sun Microsystems;20012003,http://java.sun.com/products/jms/

                                                                                                                                                                                                                                                  Java 消息服务 API,Java 2 企业版 (J2EE) 平台的一部分。

                                                                                                                                                                                                                                                  [JMS]

                                                                                                                                                                                                                                                  Java Mes­sage Ser­vice (JMS), Sun Mi­crosys­tems; 20012003, http://java.sun.com/products/jms/

                                                                                                                                                                                                                                                  The Java Mes­sage Ser­vice API, part of the Java 2, En­ter­prise Edi­tion (J2EE) plat­form.

                                                                                                                                                                                                                                                  [JMS 1.1]

                                                                                                                                                                                                                                                  Java 消息服务(Sun Java 消息服务 1.1 规范),Sun Microsystems;2002 年 4 月 12 日,http://java.sun.com/products/jms/docs.html

                                                                                                                                                                                                                                                  JMS 1.1 规范。

                                                                                                                                                                                                                                                  [JMS 1.1]

                                                                                                                                                                                                                                                  Java Mes­sage Ser­vice (the Sun Java Mes­sage Ser­vice 1.1 Spe­cific­a­tion), Sun Mi­crosys­tems; April 12, 2002, http://java.sun.com/products/jms/docs.html

                                                                                                                                                                                                                                                  The JMS 1.1 spe­cific­a­tion.

                                                                                                                                                                                                                                                  [JTA]

                                                                                                                                                                                                                                                  Java 事务 API (JTA),Sun Microsystems;20012003,http://java.sun.com/products/jta/

                                                                                                                                                                                                                                                  Java 事务 API,Java 2 企业版 (J2EE) 平台的一部分。

                                                                                                                                                                                                                                                  [JTA]

                                                                                                                                                                                                                                                  Java Trans­ac­tion API (JTA), Sun Mi­crosys­tems; 20012003, http://java.sun.com/products/jta/

                                                                                                                                                                                                                                                  The Java Trans­ac­tion API, part of the Java 2, En­ter­prise Edi­tion (J2EE) plat­form.

                                                                                                                                                                                                                                                  [Kahn]

                                                                                                                                                                                                                                                  “并行编程简单语言的语义”, G. Kahn,信息处理 74:Proc。IFIP 第 74 届大会,北荷兰出版公司,1974 年

                                                                                                                                                                                                                                                  [Kahn]

                                                                                                                                                                                                                                                  "The Se­mantics of a Simple Lan­guage for Par­al­lel Pro­gram­ming," G. Kahn, In­form­a­tion Pro­cess­ing 74: Proc. IFIP Con­gress 74, North-Hol­land Pub­lish­ing Co., 1974

                                                                                                                                                                                                                                                  [Kaye]

                                                                                                                                                                                                                                                  松散耦合:Web 服务的缺失部分, Doug Kaye;RDS Press 2003,ISBN 1881378241

                                                                                                                                                                                                                                                  对 Web 服务的全新审视。我们不用费力地阅读 API,而是以技术中立、无行话的方式了解面向服务的架构中工作的核心原理。对于渴望进行 SOAP 调用的开发人员来说,这本书可能太高级了,但对于必须向非技术人员解释这些概念的技术经理和架构师来说,它是理想的选择。

                                                                                                                                                                                                                                                  [Kaye]

                                                                                                                                                                                                                                                  Loosely Coupled: The Miss­ing Pieces of Web Ser­vices, Doug Kaye; RDS Press 2003, ISBN 1881378241

                                                                                                                                                                                                                                                  A re­fresh­ing look at Web ser­vices. In­stead of wading through APIs, we get to read about the core prin­ciples at work in ser­vice-ori­ented ar­chi­tec­tures in a tech­no­logy-neut­ral, jargon-free way. This book is likely too high-level for de­ve­lopers itch­ing to make that SOAP call, but it is ideal for tech­nical man­agers and ar­chi­tects who have to ex­plain these con­cepts to non-tech­ies.

                                                                                                                                                                                                                                                  [肯特]

                                                                                                                                                                                                                                                  数据与现实,威廉·肯特;1stBooks 2000,ISBN 1585009709

                                                                                                                                                                                                                                                  一本经典书籍(原版为 1978 年),它告诉我们为什么在计算机系统内部对现实进行建模如此困难。

                                                                                                                                                                                                                                                  [Kent]

                                                                                                                                                                                                                                                  Data and Real­ity, Wil­liam Kent; 1st­Books 2000, ISBN 1585009709

                                                                                                                                                                                                                                                  A clas­sic book (the ori­ginal edi­tion is from 1978) that tells us why mod­el­ing real­ity inside a com­puter system is so hard.

                                                                                                                                                                                                                                                  [Lewis]

                                                                                                                                                                                                                                                  MSMQ 和 MQSeries 的高级消息传递应用程序, Rhys Lewis;阙2000年,ISBN 078972023X

                                                                                                                                                                                                                                                  [Lewis]

                                                                                                                                                                                                                                                  Ad­vanced Mes­saging Ap­plic­a­tions with MSMQ and MQSer­ies, Rhys Lewis; Que 2000, ISBN 078972023X

                                                                                                                                                                                                                                                  [Leyman]

                                                                                                                                                                                                                                                  生产工作流程:概念和技术, Frank Leyman 和 Dieter Roller;普伦蒂斯·霍尔 PTR 1999,ISBN 0130217530

                                                                                                                                                                                                                                                  [Leyman]

                                                                                                                                                                                                                                                  Pro­duc­tion Work­flow: Con­cepts and Tech­niques, Frank Leyman & Dieter Roller; Pren­tice-Hall PTR 1999, ISBN 0130217530

                                                                                                                                                                                                                                                  [MDMSG]

                                                                                                                                                                                                                                                  多目标消息传递,微软;2003 年 2 月,http://msdn.microsoft.com/library/en-us/msmq/msmq_about_messages_8aqv.asp

                                                                                                                                                                                                                                                  讨论 MSMQ 3.0 中用于将消息发送到多个目标的新功能。

                                                                                                                                                                                                                                                  [MDMSG]

                                                                                                                                                                                                                                                  Mul­tiple-Des­tin­a­tion Mes­saging, Mi­crosoft; Feb­ru­ary 2003, http://msdn.mi­crosoft.com/lib­rary/en-us/msmq/ms­m­q_­about_mes­sages_8aqv.asp

                                                                                                                                                                                                                                                  Dis­cusses the new fea­ture in MSMQ 3.0 for send­ing mes­sages to more than one des­tin­a­tion.

                                                                                                                                                                                                                                                  [MicroWorkflow]

                                                                                                                                                                                                                                                  “微工作流:支持组合式面向对象软件开发的工作流架构”, Dragos Manolescu;伊利诺伊大学 2000 年,http://micro-workflow.com/PhDThesis/phdthesis.pdf

                                                                                                                                                                                                                                                  [Mi­cro­Work­flow]

                                                                                                                                                                                                                                                  "Micro-Work­flow: A Work­flow Ar­chi­tec­ture Sup­port­ing Com­pos­i­tional Object-Ori­ented Soft­ware De­vel­op­ment," Dragos Man­olescu; Uni­ver­sity of Illinois 2000, http://micro-work­flow.com/PhDThesis/phdthesis.pdf

                                                                                                                                                                                                                                                  [Monroe]

                                                                                                                                                                                                                                                  “风格化架构、设计模式和对象”, Robert T. Monroe、Drew Kompanek、Ralph Melton 和 David Garlan;1996,http://www-2.cs.cmu.edu/afs/cs/project/compose/ftp/pdf/ObjPatternsArch-ieee97.pdf

                                                                                                                                                                                                                                                  [Monroe]

                                                                                                                                                                                                                                                  "Styl­ized Ar­chi­tec­ture, Design Pat­terns, and Ob­jects," Robert T. Monroe, Drew Kom­panek, Ralph Melton, & David Garlan; 1996, http://www-2.cs.cmu.edu/afs/cs/pro­ject/com­pose/ftp/pdf/Ob­jPat­ternsArch-ieee97.pdf

                                                                                                                                                                                                                                                  [Monson-Haefel]

                                                                                                                                                                                                                                                  Java 消息服务, Richard Monson-Haefel 和 David A. Chappell;O'Reilly 2001,ISBN 0596000685

                                                                                                                                                                                                                                                  也许是最著名的 JMS 书籍。

                                                                                                                                                                                                                                                  [Monson-Haefel]

                                                                                                                                                                                                                                                  Java Mes­sage Ser­vice, Richard Monson-Haefel & David A. Chap­pell; O'Reilly 2001, ISBN 0596000685

                                                                                                                                                                                                                                                  Per­haps the best-known JMS book.

                                                                                                                                                                                                                                                  [MQSeries]

                                                                                                                                                                                                                                                  WebSphere MQ(以前称为 MQSeries),IBM;http://www.software.ibm.com/ts/mqseries最古老、最著名的消息传递和集成产品之一。

                                                                                                                                                                                                                                                  [MQSer­ies]

                                                                                                                                                                                                                                                  Web­Sphere MQ (formerly MQSer­ies), IBM; http://www.soft­ware.ibm.com/ts/mqser­ies

                                                                                                                                                                                                                                                  One of the oldest and best known mes­saging and in­teg­ra­tion products.

                                                                                                                                                                                                                                                  [MSMQ]

                                                                                                                                                                                                                                                  微软消息队列(MSMQ),微软;http://www.microsoft.com/windows2000/technologies/communications/msmq/

                                                                                                                                                                                                                                                  Windows 2000、Windows XP 和 Windows Server 2003 中内置的消息传递产品。

                                                                                                                                                                                                                                                  [MSMQ]

                                                                                                                                                                                                                                                  Mi­crosoft Mes­sage Queuing (MSMQ), Mi­crosoft; http://www.mi­crosoft.com/win­dows2000/tech­no­lo­gies/com­mu­nic­a­tions/msmq/

                                                                                                                                                                                                                                                  The mes­saging product built into Win­dows 2000, Win­dows XP, and Win­dows Server 2003.

                                                                                                                                                                                                                                                  [PatternForms]

                                                                                                                                                                                                                                                  模式形式,Wiki-Wiki-Web,Cunningham & Cunningham;最后编辑于 2002 年 8 月 26 日,http://c2.com/cgi/wiki?PatternForms 常用模式形式及其差异的

                                                                                                                                                                                                                                                  列表。

                                                                                                                                                                                                                                                  [Pat­tern­Forms]

                                                                                                                                                                                                                                                  Pat­tern Forms, Wiki-Wiki-Web, Cun­ning­ham & Cun­ning­ham; last edited on August 26, 2002, http://c2.com/cgi/wiki?Pat­tern­Forms

                                                                                                                                                                                                                                                  A list of com­monly used pat­tern forms and their dif­fer­ences.

                                                                                                                                                                                                                                                  [PLoPD1]

                                                                                                                                                                                                                                                  程序设计的模式语言,James Coplien 和 Douglas Schmidt(编辑);Addison-Wesley 1995,ISBN 0201607344

                                                                                                                                                                                                                                                  第一届 PLoP 会议的会议记录。这些会议记录包含许多论文,这些论文构成了后来书籍的基础,例如 [POSA]。本书包含 Frank Buschmann 和 Regine Meunier 的“模式系统”、Regine Meunier 的“管道和过滤器架构”以及 Diane Mularz 的“基于模式的集成架构”。

                                                                                                                                                                                                                                                  [PLoPD1]

                                                                                                                                                                                                                                                  Pat­tern Lan­guages of Pro­gram Design, James Co­plien & Douglas Schmidt (Ed­it­ors); Ad­dison-Wesley 1995, ISBN 0201607344

                                                                                                                                                                                                                                                  The pro­ceed­ings from the first PLoP con­fer­ence. These pro­ceed­ings con­tain a lot of papers that formed the basis for later books, such as [POSA]. This volume con­tains Frank Buschmann's and Regine Meunier's "A System of Pat­terns," Regine Meunier's "The Pipes and Fil­ters Ar­chi­tec­ture," and Diane Mularz's "Pat­tern-Based In­teg­ra­tion Ar­chi­tec­tures."

                                                                                                                                                                                                                                                  [PLoPD3]

                                                                                                                                                                                                                                                  程序设计模式语言 3,Robert Martin、Dirk Riehle 和 Frank Buschmann(编辑);Addison-Wesley 1998,ISBN 0201310112。PLoP会议(PLoP、EuroPLoP 等;请参阅http://hillside.net/conferencesnavigation.htm

                                                                                                                                                                                                                                                  )的第三本书包含成为 [POSA2] 基础的模式:Acceptor 和 Connector 、异步完成令牌和双重检查锁定。它还包含本书作者的两种模式:空对象和类型对象。

                                                                                                                                                                                                                                                  [PLoPD3]

                                                                                                                                                                                                                                                  Pat­tern Lan­guages of Pro­gram Design 3, Robert Martin, Dirk Riehle, & Frank Buschmann (Ed­it­ors); Ad­dison-Wesley 1998, ISBN 0201310112.

                                                                                                                                                                                                                                                  The third book from the PLoP con­fer­ences (PLoP, EuroPLoP, etc.; see http://hill­side.net/con­fer­en­cesnav­ig­a­tion.htm) con­tains pat­terns that became the basis for [POSA2]: Ac­ceptor and Con­nector, Asyn­chron­ous Com­ple­tion Token, and Double-Checked Lock­ing. It also con­tains two pat­terns from the au­thors of this book: Null Object and Type Object.

                                                                                                                                                                                                                                                  [POSA]

                                                                                                                                                                                                                                                  面向模式的软件架构, Frank Buschmann、Regine Meunier、Hans Rohnert、Peter Sommerlad 和 Michael Stal;Wiley 1996,ISBN 0471958697

                                                                                                                                                                                                                                                  一本关于架构和设计模式的好书。

                                                                                                                                                                                                                                                  [POSA]

                                                                                                                                                                                                                                                  Pat­tern-Ori­ented Soft­ware Ar­chi­tec­ture, Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Som­mer­lad, & Mi­chael Stal; Wiley 1996, ISBN 0471958697

                                                                                                                                                                                                                                                  A great book on ar­chi­tec­ture and design pat­terns.

                                                                                                                                                                                                                                                  [POSA2]

                                                                                                                                                                                                                                                  面向模式的软件架构,卷。2、道格拉斯·施密特、迈克尔·斯塔尔、汉斯·罗内特和弗兰克·布施曼;Wiley 2000,ISBN 0471606952

                                                                                                                                                                                                                                                  更多模式侧重于分布式系统和并发问题。

                                                                                                                                                                                                                                                  [POSA2]

                                                                                                                                                                                                                                                  Pat­tern-Ori­ented Soft­ware Ar­chi­tec­ture, Vol. 2, Douglas Schmidt, Mi­chael Stal, Hans Rohnert, & Frank Buschmann; Wiley 2000, ISBN 0471606952

                                                                                                                                                                                                                                                  More pat­terns fo­cused on dis­trib­uted sys­tems and con­cur­rency issues.

                                                                                                                                                                                                                                                  [Sharp]

                                                                                                                                                                                                                                                  工作流程建模:流程改进和应用程序开发工具,Alec Sharp 和 Patrick McDermott;Artech House 2001,ISBN 1580530214

                                                                                                                                                                                                                                                  本书重点关注工作流的建模方面,对于分析师和业务架构师来说是一本有趣的读物。

                                                                                                                                                                                                                                                  [Sharp]

                                                                                                                                                                                                                                                  Work­flow Mod­el­ing: Tools for Pro­cess Im­prove­ment and Ap­plic­a­tion De­vel­op­ment, Alec Sharp & Patrick Mc­Der­mott; Artech House 2001, ISBN 1580530214

                                                                                                                                                                                                                                                  This book fo­cuses on the mod­el­ing aspect of work­flowan in­ter­est­ing read for ana­lysts and busi­ness ar­chi­tects alike.

                                                                                                                                                                                                                                                  [SOAP 1.1]

                                                                                                                                                                                                                                                  W3C 简单对象访问协议 (SOAP) 1.1 规范,万维网联盟;W3C 注释,2000 年 5 月 8 日,http://www.w3.org/TR/SOAP/

                                                                                                                                                                                                                                                  SOAP 1.1 规范。

                                                                                                                                                                                                                                                  [SOAP 1.1]

                                                                                                                                                                                                                                                  W3C Simple Object Access Pro­tocol (SOAP) 1.1 Spe­cific­a­tion, World Wide Web Con­sor­tium; W3C Note, May 8, 2000, http://www.w3.org/TR/SOAP/

                                                                                                                                                                                                                                                  The SOAP 1.1 spe­cific­a­tion.

                                                                                                                                                                                                                                                  [SOAP 1.2 第 2 部分] SOAP 版本 1.2,第 2 部分:附件,万维网联盟;W3C 建议,2003 年 6 月 24 日,http://www.w3.org/TR/soap12-part2/

                                                                                                                                                                                                                                                  SOAP 1.2 规范的“额外部分”。

                                                                                                                                                                                                                                                  [SOAP 1.2 Part 2] SOAP Ver­sion 1.2, Part 2: Ad­juncts, World Wide Web Con­sor­tium; W3C Re­com­mend­a­tion, June 24, 2003, http://www.w3.org/TR/soap12-part2/

                                                                                                                                                                                                                                                  The "extra parts" of the SOAP 1.2 spe­cific­a­tion.

                                                                                                                                                                                                                                                  [Stevens]

                                                                                                                                                                                                                                                  TCP/IP 图解,第 1 卷:协议,W. Richard Stevens;艾迪生·韦斯利 1994 年,ISBN 0201633469

                                                                                                                                                                                                                                                  [Stevens]

                                                                                                                                                                                                                                                  TCP/IP Il­lus­trated, Volume 1: The Pro­to­cols, W. Richard Stevens; Ad­dison-Wesley 1994, ISBN 0201633469

                                                                                                                                                                                                                                                  [SysMsg]

                                                                                                                                                                                                                                                  “System.Messaging 命名空间”, .NET Framework,版本 1.1,Microsoft;http://msdn.microsoft.com/library/en-us/cpref/html/cpref_start.asp

                                                                                                                                                                                                                                                  [SysMsg]

                                                                                                                                                                                                                                                  "System.Mes­saging namespace," .NET Frame­work, ver­sion 1.1, Mi­crosoft; http://msdn.mi­crosoft.com/lib­rary/en-us/cpref/html/cpre­f_start.asp

                                                                                                                                                                                                                                                  [Tennison]

                                                                                                                                                                                                                                                  XSLT 和 XPath on the Edge,Jeni Tennison;约翰·威利父子公司 2001 年,ISBN 0764547763

                                                                                                                                                                                                                                                  [Ten­nison]

                                                                                                                                                                                                                                                  XSLT and XPath on the Edge, Jeni Ten­nison; John Wiley & Sons 2001, ISBN 0764547763

                                                                                                                                                                                                                                                  [UML]

                                                                                                                                                                                                                                                  UML Distilled:标准对象建模语言简要指南(第 3 版),Martin Fowler;Addison-Wesley 2003,ISBN 0321193687

                                                                                                                                                                                                                                                  学习 UML 图的优秀资源,它们应该是什么样子以及它们的含义。

                                                                                                                                                                                                                                                  [UML]

                                                                                                                                                                                                                                                  UML Dis­tilled: A Brief Guide to the Stand­ard Object Mod­el­ing Lan­guage (3rd edi­tion), Martin Fowler; Ad­dison-Wesley 2003, ISBN 0321193687

                                                                                                                                                                                                                                                  A ex­cel­lent source for learn­ing about UML dia­gram­swhat they're sup­posed to look like and what they mean.

                                                                                                                                                                                                                                                  [UMLEAI]

                                                                                                                                                                                                                                                  “企业应用程序集成的 UML 配置文件”,对象管理组;2002 年,http://www.omg.org/technology/documents/modeling_spec_catalog.htm

                                                                                                                                                                                                                                                  [UMLEAI]

                                                                                                                                                                                                                                                  "UML Pro­file for En­ter­prise Ap­plic­a­tion In­teg­ra­tion," Object Man­age­ment Group; 2002, http://www.omg.org/tech­no­logy/doc­u­ments/mod­el­in­g_spec_cata­log.htm

                                                                                                                                                                                                                                                  [Wright]

                                                                                                                                                                                                                                                  TCP/IP 图解,第 2 卷:实施,Gary R. Wright 和 W. Richard Stevens;艾迪生·韦斯利 1995 年,ISBN 020163354X

                                                                                                                                                                                                                                                  [Wright]

                                                                                                                                                                                                                                                  TCP/IP Il­lus­trated, Volume 2: The Im­ple­ment­a­tion, Gary R. Wright & W. Richard Stevens; Ad­dison-Wesley 1995, ISBN 020163354X

                                                                                                                                                                                                                                                  [WSAUS]

                                                                                                                                                                                                                                                  “Web 服务架构使用场景”,万维网联盟;W3C 工作草案,2003 年 5 月 14 日,http://www.w3.org/TR/ws-arch-scenarios/

                                                                                                                                                                                                                                                  W3C 关于如何使用 Web 服务以及规范需要满足哪些要求的最新想法。

                                                                                                                                                                                                                                                  [WSAUS]

                                                                                                                                                                                                                                                  "Web Ser­vices Ar­chi­tec­ture Usage Scen­arios," World Wide Web Con­sor­tium; W3C Work­ing Draft, May 14, 2003, http://www.w3.org/TR/ws-arch-scen­arios/

                                                                                                                                                                                                                                                  The W3C's latest think­ing on how Web ser­vices will be used and what re­quire­ments the spe­cific­a­tions need to ful­fill.

                                                                                                                                                                                                                                                  [WSDL 1.1] Web 服务描述语言 (WSDL) 1.1,万维网联盟;W3C 注释,2001 年 3 月 15 日,http://www.w3.org/TR/wsdl

                                                                                                                                                                                                                                                  WSDL 1.1 规范。

                                                                                                                                                                                                                                                  [WSDL 1.1] Web Ser­vices De­scrip­tion Lan­guage (WSDL) 1.1, World Wide Web Con­sor­tium; W3C Note March 15, 2001, http://www.w3.org/TR/wsdl

                                                                                                                                                                                                                                                  The WSDL 1.1 spe­cific­a­tion.

                                                                                                                                                                                                                                                  [WSFL]

                                                                                                                                                                                                                                                  Web 服务流语言 (WSFL) 1.0,IBM;2001 年 5 月,http://www-3.ibm.com/software/solutions/webservices/pdf/WSFL.pdf WSFL 1.0 规范。

                                                                                                                                                                                                                                                  [WSFL]

                                                                                                                                                                                                                                                  Web Ser­vices Flow Lan­guage (WSFL) 1.0, IBM; May 2001, http://www-3.ibm.com/soft­ware/solu­tions/web­ser­vices/pdf/WSFL.pdf The WSFL 1.0 spe­cific­a­tion.

                                                                                                                                                                                                                                                  [WSMQ]

                                                                                                                                                                                                                                                  使用 Java 的 WebSphere MQ(第二版),IBM;2002 年 10 月,http://publibfp.boulder.ibm.com/epubs/pdf/csqzaw11.pdf

                                                                                                                                                                                                                                                  使用 IBM 的 WebSphere MQ 消息传递产品 [MQSeries] 的 Java 程序员的开发人员指南。

                                                                                                                                                                                                                                                  [WSMQ]

                                                                                                                                                                                                                                                  Web­Sphere MQ Using Java (2nd edi­tion), IBM; Oc­to­ber 2002, http://pub­libfp.boulder.ibm.com/epubs/pdf/csqz­a­w11.pdf

                                                                                                                                                                                                                                                  The de­ve­loper's guide for Java pro­gram­mers using IBM's Web­Sphere MQ mes­saging product [MQSer­ies].

                                                                                                                                                                                                                                                  [XML 1.0]

                                                                                                                                                                                                                                                  可扩展标记语言 (XML) 1.0(第 2 版),万维网联盟;W3C 建议,2000 年 10 月 6 日,http://www.w3.org/TR/REC-xml

                                                                                                                                                                                                                                                  XML 1.0 规范。

                                                                                                                                                                                                                                                  [XML 1.0]

                                                                                                                                                                                                                                                  Ex­tens­ible Markup Lan­guage (XML) 1.0 (2nd edi­tion), World Wide Web Con­sor­tium; W3C Re­com­mend­a­tion, Oc­to­ber 6, 2000, http://www.w3.org/TR/REC-xml

                                                                                                                                                                                                                                                  The XML 1.0 spe­cific­a­tion.

                                                                                                                                                                                                                                                  [XSLT 1.0]

                                                                                                                                                                                                                                                  XSL 转换 (XSLT) 1.0 版,万维网联盟;W3C 建议,1999 年11 月16 日, http://www.w3.org/TR/xslt XSLT

                                                                                                                                                                                                                                                  1.0 规范。

                                                                                                                                                                                                                                                  [XSLT 1.0]

                                                                                                                                                                                                                                                  XSL Trans­form­a­tions (XSLT) Ver­sion 1.0, World Wide Web Con­sor­tium; W3C Re­com­mend­a­tion, Novem­ber 16, 1999, http://www.w3.org/TR/xslt

                                                                                                                                                                                                                                                  The XSLT 1.0 spe­cific­a­tion.

                                                                                                                                                                                                                                                  [Waldo]

                                                                                                                                                                                                                                                  “分布式计算说明”(技术报告SMLI TR-94-29),Jim Waldo、Geoff Wyant、Ann Wollrath 和 Sam Kendall;Sun Microsystems 实验室,1994 年 11 月,http://citeseer.nj.nec.com/waldo94note.html

                                                                                                                                                                                                                                                  [Waldo]

                                                                                                                                                                                                                                                  "A Note on Dis­trib­uted Com­put­ing" (Tech­nical Report SMLI TR-94-29), Jim Waldo, Geoff Wyant, Ann Wollrath, & Sam Kend­all; Sun Mi­crosys­tems Labor­at­or­ies, Novem­ber 1994, http://cite­seer.nj.nec.com/wal­do94­note.html

                                                                                                                                                                                                                                                  [Zahavi]

                                                                                                                                                                                                                                                  企业应用程序与 CORBA 集成,Ron Zahavi;约翰·威利父子公司 1999 年,ISBN 0471327204

                                                                                                                                                                                                                                                  [Zahavi]

                                                                                                                                                                                                                                                  En­ter­prise Ap­plic­a­tion In­teg­ra­tion with CORBA, Ron Zahavi; John Wiley & Sons 1999, ISBN 0471327204

                                                                                                                                                                                                                                                    模式列表

                                                                                                                                                                                                                                                    List of Patterns

                                                                                                                                                                                                                                                    图形/ar01fig01.gif

                                                                                                                                                                                                                                                    聚合器 我们如何组合各个但相关的消息的结果,以便可以将它们作为一个整体进行处理?

                                                                                                                                                                                                                                                    Ag­greg­ator How do we com­bine the res­ults of in­di­vidual but re­lated mes­sages so that they can be pro­cessed as a whole?

                                                                                                                                                                                                                                                    规范数据模型 在集成使用不同数据格式的应用程序时,如何最大限度地减少依赖性?

                                                                                                                                                                                                                                                    Ca­non­ical Data Model How can you min­im­ize de­pend­en­cies when in­teg­rat­ing ap­plic­a­tions that use dif­fer­ent data formats?

                                                                                                                                                                                                                                                    图形/ar01fig02.gif

                                                                                                                                                                                                                                                    通道适配器如何

                                                                                                                                                                                                                                                    Chan­nel Ad­apter How can you con­nect an ap­plic­a­tion to the mes­saging system so that it can send and re­ceive mes­sages?

                                                                                                                                                                                                                                                    图形/ar01fig03.gif

                                                                                                                                                                                                                                                    通道清除器 如何防止通道上的剩余消息干扰测试或正在运行的系统?

                                                                                                                                                                                                                                                    Chan­nel Purger How can you keep leftover mes­sages on a chan­nel from dis­turb­ing tests or run­ning sys­tems?

                                                                                                                                                                                                                                                    图形/ar01fig04.gif

                                                                                                                                                                                                                                                    索赔检查如何在不牺牲信息内容的情况下减少整个系统发送的消息的数据量?

                                                                                                                                                                                                                                                    Claim Check How can we reduce the data volume of mes­sage sent across the system without sac­ri­fi­cing in­form­a­tion con­tent?

                                                                                                                                                                                                                                                    图形/ar01fig05.gif

                                                                                                                                                                                                                                                    命令消息 如何

                                                                                                                                                                                                                                                    Com­mand Mes­sage How can mes­saging be used to invoke a pro­ced­ure in an­other ap­plic­a­tion?

                                                                                                                                                                                                                                                    图形/ar01fig06.gif

                                                                                                                                                                                                                                                    竞争 消费者

                                                                                                                                                                                                                                                    Com­pet­ing Con­sumers How can a mes­saging client pro­cess mul­tiple mes­sages con­cur­rently?

                                                                                                                                                                                                                                                    图形/ar01fig07.gif

                                                                                                                                                                                                                                                    组合消息处理器

                                                                                                                                                                                                                                                    Com­posed Mes­sage Pro­cessor How can you main­tain the over­all mes­sage flow when pro­cess­ing a mes­sage con­sist­ing of mul­tiple ele­ments, each of which may re­quire dif­fer­ent pro­cess­ing?

                                                                                                                                                                                                                                                    图形/ar01fig08.gif

                                                                                                                                                                                                                                                    内容丰富器 如果消息发起者没有提供所有必需的数据项,我们如何

                                                                                                                                                                                                                                                    Con­tent En­richer How do we com­mu­nic­ate with an­other system if the mes­sage ori­gin­ator does not have all the re­quired data items avail­able?

                                                                                                                                                                                                                                                    图形/ar01fig09.gif

                                                                                                                                                                                                                                                    内容过滤器当您只对少数数据项感兴趣时,如何简化对大消息的处理?

                                                                                                                                                                                                                                                    Con­tent Filter How do you sim­plify deal­ing with a large mes­sage when you are in­ter­ested only in a few data items?

                                                                                                                                                                                                                                                    图形/ar01fig10.gif

                                                                                                                                                                                                                                                    基于内容的路由器我们如何处理单个逻辑功能的实现分布在多个物理系统上的情况?

                                                                                                                                                                                                                                                    Con­tent-Based Router How do we handle a situ­ation in which the im­ple­ment­a­tion of a single lo­gical func­tion is spread across mul­tiple phys­ical sys­tems?

                                                                                                                                                                                                                                                    图形/ar01fig11.gif

                                                                                                                                                                                                                                                    控制总线 我们如何有效地管理分布在多个平台和广泛地理区域的消息传递系统?

                                                                                                                                                                                                                                                    Con­trol Bus How can we ef­fect­ively ad­min­is­ter a mes­saging system that is dis­trib­uted across mul­tiple plat­forms and a wide geo­graphic area?

                                                                                                                                                                                                                                                    图形/ar01fig12.gif

                                                                                                                                                                                                                                                    相关标识符 收到回复的请求者如何知道这是哪个请求的回复?

                                                                                                                                                                                                                                                    Cor­rel­a­tion Iden­ti­fier How does a re­questor that has re­ceived a reply know which re­quest this is the reply for?

                                                                                                                                                                                                                                                    图形/ar01fig13.gif

                                                                                                                                                                                                                                                    数据类型 Channel应用

                                                                                                                                                                                                                                                    Data­type Chan­nel How can the ap­plic­a­tion send a data item such that the re­ceiver will know how to pro­cess it?

                                                                                                                                                                                                                                                    图形/ar01fig14.gif

                                                                                                                                                                                                                                                    死信通道 消息系统将如何

                                                                                                                                                                                                                                                    Dead Letter Chan­nel What will the mes­saging system do with a mes­sage it cannot de­liver?

                                                                                                                                                                                                                                                    图形/ar01fig15.gif

                                                                                                                                                                                                                                                    迂回 如何通过中间步骤路由消息以执行验证、测试或调试功能?

                                                                                                                                                                                                                                                    Detour How can you route a mes­sage through in­ter­me­di­ate steps to per­form val­id­a­tion, test­ing, or de­bug­ging func­tions?

                                                                                                                                                                                                                                                    图形/ar01fig16.gif

                                                                                                                                                                                                                                                    文档消息 如何

                                                                                                                                                                                                                                                    Doc­u­ment Mes­sage How can mes­saging be used to trans­fer data between ap­plic­a­tions?

                                                                                                                                                                                                                                                    图形/ar01fig17.gif

                                                                                                                                                                                                                                                    持久订阅者 订阅者 如何

                                                                                                                                                                                                                                                    Dur­able Sub­scriber How can a sub­scriber avoid miss­ing mes­sages while it's not listen­ing for them?

                                                                                                                                                                                                                                                    图形/ar01fig18.gif

                                                                                                                                                                                                                                                    动态路由器如何避免路由器对所有可能目的地的依赖,同时保持其效率?

                                                                                                                                                                                                                                                    Dy­namic Router How can you avoid the de­pend­ency of the router on all pos­sible des­tin­a­tions while main­tain­ing its ef­fi­ciency?

                                                                                                                                                                                                                                                    图形/ar01fig19.gif

                                                                                                                                                                                                                                                    信封包装

                                                                                                                                                                                                                                                    En­vel­ope Wrap­per How can ex­ist­ing sys­tems par­ti­cip­ate in a mes­saging ex­change that places spe­cific re­quire­ments, such as mes­sage header fields or en­cryp­tion, on the mes­sage format?

                                                                                                                                                                                                                                                    图形/ar01fig20.gif

                                                                                                                                                                                                                                                    事件消息 如何

                                                                                                                                                                                                                                                    Event Mes­sage How can mes­saging be used to trans­mit events from one ap­plic­a­tion to an­other?

                                                                                                                                                                                                                                                    图形/ar01fig21.gif

                                                                                                                                                                                                                                                    事件驱动的使用者应用程序 如何

                                                                                                                                                                                                                                                    Event-Driven Con­sumer How can an ap­plic­a­tion auto­mat­ic­ally con­sume mes­sages as they become avail­able?

                                                                                                                                                                                                                                                    图形/ar01fig22.gif

                                                                                                                                                                                                                                                    文件传输如何

                                                                                                                                                                                                                                                    File Trans­fer How can I in­teg­rate mul­tiple ap­plic­a­tions so that they work to­gether and can ex­change in­form­a­tion?

                                                                                                                                                                                                                                                    格式指示器 如何 设计消息的数据格式以适应未来可能的变化?

                                                                                                                                                                                                                                                    Format In­dic­ator How can a mes­sage's data format be de­signed to allow for pos­sible future changes?

                                                                                                                                                                                                                                                    图形/ar01fig23.gif

                                                                                                                                                                                                                                                    保证传送发送者如何确保即使消息传递系统出现故障也能传送消息?

                                                                                                                                                                                                                                                    Guar­an­teed De­liv­ery How can the sender make sure that a mes­sage will be de­livered even if the mes­saging system fails?

                                                                                                                                                                                                                                                    幂等接收者 消息接收者 如何

                                                                                                                                                                                                                                                    Idem­potent Re­ceiver How can a mes­sage re­ceiver deal with du­plic­ate mes­sages?

                                                                                                                                                                                                                                                    图形/ar01fig24.gif

                                                                                                                                                                                                                                                    无效的消息通道 消息接收者 如何

                                                                                                                                                                                                                                                    In­valid Mes­sage Chan­nel How can a mes­saging re­ceiver grace­fully handle re­ceiv­ing a mes­sage that makes no sense?

                                                                                                                                                                                                                                                    图形/ar01fig25.gif

                                                                                                                                                                                                                                                    消息代理 如何将消息的目的地与发送者分离并保持对消息流的集中控制?

                                                                                                                                                                                                                                                    Mes­sage Broker How can you de­couple the des­tin­a­tion of a mes­sage from the sender and main­tain cent­ral con­trol over the flow of mes­sages?

                                                                                                                                                                                                                                                    图形/ar01fig26.gif

                                                                                                                                                                                                                                                    消息总线 哪种架构能够使单独的应用程序以解耦的方式协同工作,以便可以轻松添加或删除应用程序而不影响其他应用程序?

                                                                                                                                                                                                                                                    Mes­sage Bus What ar­chi­tec­ture en­ables sep­ar­ate ap­plic­a­tions to work to­gether but in a de­coupled fash­ion such that ap­plic­a­tions can be easily added or re­moved without af­fect­ing the others?

                                                                                                                                                                                                                                                    图形/ar01fig27.gif

                                                                                                                                                                                                                                                    消息通道 一个应用程序 如何

                                                                                                                                                                                                                                                    Mes­sage Chan­nel How does one ap­plic­a­tion com­mu­nic­ate with an­other using mes­saging?

                                                                                                                                                                                                                                                    图形/ar01fig28.gif

                                                                                                                                                                                                                                                    消息调度 程序

                                                                                                                                                                                                                                                    Mes­sage Dis­patcher How can mul­tiple con­sumers on a single chan­nel co­ordin­ate their mes­sage pro­cess­ing?

                                                                                                                                                                                                                                                    图形/ar01fig29.gif

                                                                                                                                                                                                                                                    消息端点 应用程序如何连接到消息通道来发送和接收消息?

                                                                                                                                                                                                                                                    Mes­sage En­d­point How does an ap­plic­a­tion con­nect to a mes­saging chan­nel to send and re­ceive Mes­sages?

                                                                                                                                                                                                                                                    图形/ar01fig30.gif

                                                                                                                                                                                                                                                    消息过期 发件人 如何

                                                                                                                                                                                                                                                    Mes­sage Ex­pir­a­tion How can a sender in­dic­ate when a mes­sage should be con­sidered stale and thus shouldn't be pro­cessed?

                                                                                                                                                                                                                                                    图形/ar01fig31.gif

                                                                                                                                                                                                                                                    消息过滤器 组件如何避免接收不感兴趣的消息?

                                                                                                                                                                                                                                                    Mes­sage Filter How can a com­pon­ent avoid re­ceiv­ing un­in­ter­est­ing mes­sages?

                                                                                                                                                                                                                                                    消息历史记录 我们 如何

                                                                                                                                                                                                                                                    Mes­sage His­tory How can we ef­fect­ively ana­lyze and debug the flow of mes­sages in a loosely coupled system?

                                                                                                                                                                                                                                                    图形/ar01fig32.gif

                                                                                                                                                                                                                                                    消息路由器 如何解耦各个处理步骤,以便消息可以根据一组条件传递到不同的过滤器?

                                                                                                                                                                                                                                                    Mes­sage Router How can you de­couple in­di­vidual pro­cess­ing steps so that mes­sages can be passed to dif­fer­ent fil­ters de­pend­ing on a set of con­di­tions?

                                                                                                                                                                                                                                                    图形/ar01fig33.gif

                                                                                                                                                                                                                                                    消息 序列

                                                                                                                                                                                                                                                    Mes­sage Se­quence How can mes­saging trans­mit an ar­bit­rar­ily large amount of data?

                                                                                                                                                                                                                                                    图形/ar01fig34.gif

                                                                                                                                                                                                                                                    消息存储 我们如何在不影响消息传递系统的松散耦合和瞬态性质的情况下报告消息信息?

                                                                                                                                                                                                                                                    Mes­sage Store How can we report against mes­sage in­form­a­tion without dis­turb­ing the loosely coupled and tran­si­ent nature of a mes­saging system?

                                                                                                                                                                                                                                                    图形/ar01fig35.gif

                                                                                                                                                                                                                                                    消息转换器 使用不同数据格式的系统 如何

                                                                                                                                                                                                                                                    Mes­sage Trans­lator How can sys­tems using dif­fer­ent data formats com­mu­nic­ate with each other using mes­saging?

                                                                                                                                                                                                                                                    图形/ar01fig36.gif

                                                                                                                                                                                                                                                    消息通过消息通道连接的两个应用程序如何交换信息?

                                                                                                                                                                                                                                                    Mes­sage How can two ap­plic­a­tions con­nec­ted by a mes­sage chan­nel ex­change a piece of in­form­a­tion?

                                                                                                                                                                                                                                                    图形/ar01fig37.gif

                                                                                                                                                                                                                                                    消息传递桥 如何连接多个消息传递系统,以便一个系统上可用的消息在其他系统上也可用?

                                                                                                                                                                                                                                                    Mes­saging Bridge How can mul­tiple mes­saging sys­tems be con­nec­ted so that mes­sages avail­able on one are also avail­able on the others?

                                                                                                                                                                                                                                                    图形/ar01fig38.gif

                                                                                                                                                                                                                                                    消息传递网关 如何

                                                                                                                                                                                                                                                    Mes­saging Gate­way How do you en­cap­su­late access to the mes­saging system from the rest of the ap­plic­a­tion?

                                                                                                                                                                                                                                                    消息传递映射器 如何在域对象和消息传递基础结构之间移动数据,同时保持两者相互独立?

                                                                                                                                                                                                                                                    Mes­saging Mapper How do you move data between domain ob­jects and the mes­saging in­fra­struc­ture while keep­ing the two in­de­pend­ent of each other?

                                                                                                                                                                                                                                                    图形/ar01fig39.gif

                                                                                                                                                                                                                                                    消息传递 如何

                                                                                                                                                                                                                                                    Mes­saging How can I in­teg­rate mul­tiple ap­plic­a­tions so that they work to­gether and can ex­change in­form­a­tion?

                                                                                                                                                                                                                                                    图形/ar01fig40.gif

                                                                                                                                                                                                                                                    规范化器 如何 处理语义上相同但以不同格式到达的消息?

                                                                                                                                                                                                                                                    Nor­mal­izer How do you pro­cess mes­sages that are se­mantic­ally equi­val­ent but arrive in a dif­fer­ent format?

                                                                                                                                                                                                                                                    图形/ar01fig41.gif

                                                                                                                                                                                                                                                    管道和过滤器 我们 如何

                                                                                                                                                                                                                                                    Pipes and Fil­ters How can we per­form com­plex pro­cess­ing on a mes­sage while main­tain­ing in­de­pend­ence and flex­ib­il­ity?

                                                                                                                                                                                                                                                    图形/ar01fig42.gif

                                                                                                                                                                                                                                                    点对点通道 呼叫者 如何

                                                                                                                                                                                                                                                    Point-to-Point Chan­nel How can the caller be sure that ex­actly one re­ceiver will re­ceive the doc­u­ment or per­form the call?

                                                                                                                                                                                                                                                    图形/ar01fig43.gif

                                                                                                                                                                                                                                                    轮询 消费者

                                                                                                                                                                                                                                                    Polling Con­sumer How can an ap­plic­a­tion con­sume a mes­sage when the ap­plic­a­tion is ready?

                                                                                                                                                                                                                                                    图形/ar01fig44.gif

                                                                                                                                                                                                                                                    流程管理器 当所需的步骤在设计时可能未知并且可能不连续时,我们 如何

                                                                                                                                                                                                                                                    Pro­cess Man­ager How do we route a mes­sage through mul­tiple pro­cess­ing steps when the re­quired steps may not be known at design time and may not be se­quen­tial?

                                                                                                                                                                                                                                                    图形/ar01fig45.gif

                                                                                                                                                                                                                                                    发布-订阅通道 发送者如何

                                                                                                                                                                                                                                                    Pub­lish-Sub­scribe Chan­nel How can the sender broad­cast an event to all in­ter­ested re­ceiv­ers?

                                                                                                                                                                                                                                                    图形/ar01fig46.gif

                                                                                                                                                                                                                                                    收件人列表我们如何将邮件路由到动态收件人列表?

                                                                                                                                                                                                                                                    Re­cip­i­ent List How do we route a mes­sage to a dy­namic list of re­cip­i­ents?

                                                                                                                                                                                                                                                    图形/ar01fig47.gif

                                                                                                                                                                                                                                                    远程过程调用如何

                                                                                                                                                                                                                                                    Remote Pro­ced­ure In­voc­a­tion How can I in­teg­rate mul­tiple ap­plic­a­tions so that they work to­gether and can ex­change in­form­a­tion?

                                                                                                                                                                                                                                                    图形/ar01fig48.gif

                                                                                                                                                                                                                                                    请求-回复 当应用程序发送消息时,如何从接收者那里得到响应?

                                                                                                                                                                                                                                                    Re­quest-Reply When an ap­plic­a­tion sends a mes­sage, how can it get a re­sponse from the re­ceiver?

                                                                                                                                                                                                                                                    图形/ar01fig49.gif

                                                                                                                                                                                                                                                    重排序器 我们 如何

                                                                                                                                                                                                                                                    Resequen­cer How can we get a stream of re­lated but out-of-se­quence mes­sages back into the cor­rect order?

                                                                                                                                                                                                                                                    图形/ar01fig50.gif

                                                                                                                                                                                                                                                    回复地址 回复者 如何

                                                                                                                                                                                                                                                    Return Ad­dress How does a replier know where to send the reply?

                                                                                                                                                                                                                                                    图形/ar01fig51.gif

                                                                                                                                                                                                                                                    路由表当设计时步骤顺序未知并且每条消息的步骤顺序可能不同时,我们如何通过一系列处理步骤连续路由消息?

                                                                                                                                                                                                                                                    Rout­ing Slip How do we route a mes­sage con­sec­ut­ively through a series of pro­cess­ing steps when the se­quence of steps is not known at design time and may vary for each mes­sage?

                                                                                                                                                                                                                                                    分散-收集当一条消息必须发送给多个收件人且每个收件人都可以发送回复时,如何维护整个消息流?

                                                                                                                                                                                                                                                    Scat­ter-Gather How do you main­tain the over­all mes­sage flow when a mes­sage must be sent to mul­tiple re­cip­i­ents, each of which may send a reply?

                                                                                                                                                                                                                                                    图形/ar01fig52.gif

                                                                                                                                                                                                                                                    选择性消费者消息消费者 如何

                                                                                                                                                                                                                                                    Se­lect­ive Con­sumer How can a mes­sage con­sumer select which mes­sages it wishes to re­ceive?

                                                                                                                                                                                                                                                    图形/ar01fig53.gif

                                                                                                                                                                                                                                                    服务激活器 应用 程序如何设计可通过各种消息传递技术和非消息传递技术调用的服务?

                                                                                                                                                                                                                                                    Ser­vice Ac­tiv­ator How can an ap­plic­a­tion design a ser­vice to be in­voked both via vari­ous mes­saging tech­no­lo­gies and via non-mes­saging tech­niques?

                                                                                                                                                                                                                                                    图形/ar01fig54.gif

                                                                                                                                                                                                                                                    共享数据库 如何

                                                                                                                                                                                                                                                    Shared Data­base How can I in­teg­rate mul­tiple ap­plic­a­tions so that they work to­gether and can ex­change in­form­a­tion?

                                                                                                                                                                                                                                                    图形/ar01fig55.gif

                                                                                                                                                                                                                                                    智能代理 如何跟踪向请求者指定的返回地址发布回复消息的服务上的消息?

                                                                                                                                                                                                                                                    Smart Proxy How can you track mes­sages on a ser­vice that pub­lishes reply mes­sages to the Return Ad­dress spe­cified by the re­questor?

                                                                                                                                                                                                                                                    图形/ar01fig56.gif

                                                                                                                                                                                                                                                    如果消息包含多个元素,并且每个元素可能必须以不同的方式处理,那么我们如何处理该消息?

                                                                                                                                                                                                                                                    Split­ter How can we pro­cess a mes­sage if it con­tains mul­tiple ele­ments, each of which may have to be pro­cessed in a dif­fer­ent way?

                                                                                                                                                                                                                                                    图形/ar01fig57.gif

                                                                                                                                                                                                                                                    测试消息 如果组件正在主动处理消息,但由于内部故障而导致传出消息出现乱码,会发生 什么

                                                                                                                                                                                                                                                    Test Mes­sage What hap­pens if a com­pon­ent is act­ively pro­cess­ing mes­sages but garbles out­go­ing mes­sages due to an in­ternal fault?

                                                                                                                                                                                                                                                    图形/ar01fig58.gif

                                                                                                                                                                                                                                                    事务客户端 客户端如何控制其与消息系统的事务?

                                                                                                                                                                                                                                                    Trans­ac­tional Client How can a client con­trol its trans­ac­tions with the mes­saging system?

                                                                                                                                                                                                                                                    图形/ar01fig59.gif

                                                                                                                                                                                                                                                    Wire Tap 如何检查在点对点通道上传输的消息?

                                                                                                                                                                                                                                                    Wire Tap How do you in­spect mes­sages that travel on a Point-to-Point Chan­nel?

                                                                                                                                                                                                                                                      企业集成模式

                                                                                                                                                                                                                                                      Enterprise Integration Patterns

                                                                                                                                                                                                                                                      图形/ar01fig60.jpg

                                                                                                                                                                                                                                                      图形/ar01fig60a.gif